@dxos/react-ui-table 0.8.4-main.f9ba587 → 0.8.4-main.fd6878d

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 (122) hide show
  1. package/dist/lib/browser/chunk-L2LSB6KV.mjs +18 -0
  2. package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-ZAOSIG3L.mjs +1814 -0
  4. package/dist/lib/browser/chunk-ZAOSIG3L.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +658 -2359
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/testing/index.mjs +123 -0
  9. package/dist/lib/browser/testing/index.mjs.map +7 -0
  10. package/dist/lib/browser/types/index.mjs +3 -5
  11. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +20 -0
  12. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +7 -0
  13. package/dist/lib/node-esm/chunk-ZOFFI3JJ.mjs +1815 -0
  14. package/dist/lib/node-esm/chunk-ZOFFI3JJ.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +658 -2359
  16. package/dist/lib/node-esm/index.mjs.map +4 -4
  17. package/dist/lib/node-esm/meta.json +1 -1
  18. package/dist/lib/node-esm/testing/index.mjs +124 -0
  19. package/dist/lib/node-esm/testing/index.mjs.map +7 -0
  20. package/dist/lib/node-esm/types/index.mjs +3 -5
  21. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -1
  22. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  23. package/dist/types/src/components/Table/ColumnSettings.d.ts +4 -2
  24. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  25. package/dist/types/src/components/Table/CreateRefPanel.d.ts +1 -1
  26. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/DynamicTable.d.ts +1 -1
  28. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +1 -1
  30. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  31. package/dist/types/src/components/Table/{relations.stories.d.ts → Relations.stories.d.ts} +2 -2
  32. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -0
  33. package/dist/types/src/components/Table/RowActionsMenu.d.ts +1 -1
  34. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  35. package/dist/types/src/components/Table/Table.d.ts +1 -0
  36. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  37. package/dist/types/src/components/Table/Table.stories.d.ts +1 -1
  38. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  39. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
  40. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +2 -1
  41. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  42. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +8 -7
  43. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  44. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  45. package/dist/types/src/components/TableCellEditor/extension.d.ts.map +1 -1
  46. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
  47. package/dist/types/src/components/testing.d.ts +3 -40
  48. package/dist/types/src/components/testing.d.ts.map +1 -1
  49. package/dist/types/src/hooks/useTableModel.d.ts +8 -7
  50. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  51. package/dist/types/src/index.d.ts +0 -1
  52. package/dist/types/src/index.d.ts.map +1 -1
  53. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  54. package/dist/types/src/model/selection-model.d.ts +2 -1
  55. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  56. package/dist/types/src/model/table-model.d.ts +17 -16
  57. package/dist/types/src/model/table-model.d.ts.map +1 -1
  58. package/dist/types/src/model/table-presentation.d.ts +2 -1
  59. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  60. package/dist/types/src/model/table-sorting.d.ts +4 -4
  61. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  62. package/dist/types/src/testing/index.d.ts +2 -0
  63. package/dist/types/src/testing/index.d.ts.map +1 -0
  64. package/dist/types/src/testing/useTestTableModel.d.ts +41 -0
  65. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -0
  66. package/dist/types/src/translations.d.ts +2 -2
  67. package/dist/types/src/types/index.d.ts +1 -1
  68. package/dist/types/src/types/index.d.ts.map +1 -1
  69. package/dist/types/src/types/schema.d.ts +8 -0
  70. package/dist/types/src/types/schema.d.ts.map +1 -0
  71. package/dist/types/src/util/dynamic-table.d.ts +3 -4
  72. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  73. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  74. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  75. package/dist/types/src/util/table.d.ts +9 -11
  76. package/dist/types/src/util/table.d.ts.map +1 -1
  77. package/dist/types/tsconfig.tsbuildinfo +1 -1
  78. package/package.json +50 -36
  79. package/src/components/Table/ColumnActionsMenu.tsx +1 -1
  80. package/src/components/Table/ColumnSettings.tsx +15 -11
  81. package/src/components/Table/CreateRefPanel.tsx +6 -4
  82. package/src/components/Table/DynamicTable.stories.tsx +3 -2
  83. package/src/components/Table/DynamicTable.tsx +17 -10
  84. package/src/components/Table/{relations.stories.tsx → Relations.stories.tsx} +36 -35
  85. package/src/components/Table/RowActionsMenu.tsx +3 -3
  86. package/src/components/Table/Table.stories.tsx +99 -144
  87. package/src/components/Table/Table.tsx +87 -71
  88. package/src/components/TableCellEditor/CellValidationMessage.tsx +1 -1
  89. package/src/components/TableCellEditor/FormCellEditor.tsx +30 -21
  90. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +24 -27
  91. package/src/components/TableCellEditor/TableCellEditor.tsx +42 -27
  92. package/src/components/TableCellEditor/extension.ts +2 -2
  93. package/src/components/TableToolbar/TableToolbar.tsx +2 -2
  94. package/src/components/testing.ts +7 -24
  95. package/src/hooks/useTableModel.ts +21 -17
  96. package/src/index.ts +0 -1
  97. package/src/model/modal-controller.ts +1 -1
  98. package/src/model/selection-model.ts +6 -3
  99. package/src/model/table-model.test.ts +8 -8
  100. package/src/model/table-model.ts +68 -62
  101. package/src/model/table-presentation.test.ts +8 -8
  102. package/src/model/table-presentation.ts +35 -13
  103. package/src/model/table-sorting.ts +11 -11
  104. package/src/playwright/smoke.spec.ts +105 -14
  105. package/src/testing/index.ts +5 -0
  106. package/src/testing/useTestTableModel.ts +123 -0
  107. package/src/translations.ts +2 -2
  108. package/src/types/index.ts +2 -2
  109. package/src/types/schema.ts +16 -0
  110. package/src/util/dynamic-table.ts +20 -18
  111. package/src/util/schema.ts +1 -1
  112. package/src/util/table-buttons.ts +2 -1
  113. package/src/util/table-controls.ts +4 -1
  114. package/src/util/table.ts +37 -111
  115. package/dist/lib/browser/chunk-Q5QQXJUO.mjs +0 -27
  116. package/dist/lib/browser/chunk-Q5QQXJUO.mjs.map +0 -7
  117. package/dist/lib/node-esm/chunk-ZNOU6LHG.mjs +0 -29
  118. package/dist/lib/node-esm/chunk-ZNOU6LHG.mjs.map +0 -7
  119. package/dist/types/src/components/Table/relations.stories.d.ts.map +0 -1
  120. package/dist/types/src/types/table.d.ts +0 -56
  121. package/dist/types/src/types/table.d.ts.map +0 -1
  122. package/src/types/table.ts +0 -27
@@ -1,8 +1,24 @@
1
1
  import { createRequire } from 'node:module';const require = createRequire(import.meta.url);
2
2
  import {
3
- TableSchema,
4
- TableType
5
- } from "./chunk-ZNOU6LHG.mjs";
3
+ BUTTON_IDENTIFIERS,
4
+ CONTROL_IDENTIFIERS,
5
+ CheckboxStory,
6
+ ModalController,
7
+ SwitchStory,
8
+ TableModel,
9
+ TablePresentation,
10
+ cellClassesForRowSelection,
11
+ createTable,
12
+ getBaseSchema,
13
+ makeDynamicTable,
14
+ narrowSchema,
15
+ tableButtons,
16
+ tableControls,
17
+ touch,
18
+ useAddRow,
19
+ useTableModel
20
+ } from "./chunk-ZOFFI3JJ.mjs";
21
+ import "./chunk-NXZ4CJYX.mjs";
6
22
 
7
23
  // src/translations.ts
8
24
  var translationKey = "react-ui-table";
@@ -14,1956 +30,65 @@ var translations = [
14
30
  "create object label": "Create table",
15
31
  "table name placeholder": "Table name",
16
32
  "settings title": "Table settings",
17
- "table schema label": "Schema",
18
- "new schema": "New schema",
33
+ "table schema label": "Record type",
34
+ "new schema": "New record type",
19
35
  "continue label": "Continue",
20
- "create stack section label": "Create table",
21
- "add row label": "Add row",
22
- "create comment": "Create thread",
23
- "column action sort descending": "Sort Descending",
24
- "column action sort ascending": "Sort Ascending",
25
- "column action clear sorting": "Clear Sorting",
26
- "column action settings": "Column Settings",
27
- "column action delete": "Delete Column",
28
- "delete row label": "Delete row",
29
- "bulk delete row label": "Delete selected rows",
30
- "column deleted label": "Column deleted",
31
- "new column button label": "Create column",
32
- "create new object label": "Create new object \u201C{{text}}\u201D",
33
- "save view label": "Save changes to view"
34
- }
35
- }
36
- }
37
- ];
38
-
39
- // src/components/Table/Table.tsx
40
- import { useSignals as _useSignals8 } from "@preact-signals/safe-react/tracking";
41
- import React8, { forwardRef, useCallback as useCallback5, useImperativeHandle, useState as useState3, useMemo as useMemo5, useEffect as useEffect4 } from "react";
42
- import { getValue as getValue4 } from "@dxos/echo-schema";
43
- import { invariant as invariant4 } from "@dxos/invariant";
44
- import { Filter } from "@dxos/react-client/echo";
45
- import { useTranslation as useTranslation3 } from "@dxos/react-ui";
46
- import { useAttention } from "@dxos/react-ui-attention";
47
- import { closestCell, Grid, gridSeparatorInlineEnd, gridSeparatorBlockEnd } from "@dxos/react-ui-grid";
48
- import { mx as mx2 } from "@dxos/react-ui-theme";
49
- import { isNotFalsy, safeParseInt } from "@dxos/util";
50
-
51
- // src/components/Table/ColumnActionsMenu.tsx
52
- import { useSignals as _useSignals } from "@preact-signals/safe-react/tracking";
53
- import React from "react";
54
- import { DropdownMenu, useTranslation } from "@dxos/react-ui";
55
- var ColumnActionsMenu = ({ model, modals }) => {
56
- var _effect = _useSignals();
57
- try {
58
- const { t } = useTranslation(translationKey);
59
- const state = modals.state.value;
60
- if (state?.type !== "column") {
61
- return null;
62
- }
63
- const currentSort = model.sorting?.sorting;
64
- const isCurrentColumnSorted = currentSort?.fieldId === state.fieldId;
65
- return /* @__PURE__ */ React.createElement(DropdownMenu.Root, {
66
- modal: false,
67
- open: true,
68
- onOpenChange: modals.close
69
- }, /* @__PURE__ */ React.createElement(DropdownMenu.VirtualTrigger, {
70
- virtualRef: modals.trigger
71
- }), /* @__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, {
72
- "data-testid": "column-sort-descending",
73
- onClick: () => model.sorting?.setSort(state.fieldId, "desc")
74
- }, t("column action sort descending")), (!isCurrentColumnSorted || currentSort?.direction === "desc") && /* @__PURE__ */ React.createElement(DropdownMenu.Item, {
75
- "data-testid": "column-sort-ascending",
76
- onClick: () => model.sorting?.setSort(state.fieldId, "asc")
77
- }, t("column action sort ascending")), isCurrentColumnSorted && /* @__PURE__ */ React.createElement(DropdownMenu.Item, {
78
- "data-testid": "column-clear-sort",
79
- onClick: () => model.sorting?.clearSort()
80
- }, t("column action clear sorting")), model.getColumnCount() > 1 && model.features.schemaEditable && /* @__PURE__ */ React.createElement(DropdownMenu.Item, {
81
- "data-testid": "column-delete",
82
- onClick: () => model.deleteColumn(state.fieldId)
83
- }, t("column action delete")), model.features.schemaEditable && /* @__PURE__ */ React.createElement(DropdownMenu.Item, {
84
- "data-testid": "column-settings",
85
- onClick: () => modals.openColumnSettings()
86
- }, t("column action settings"))), /* @__PURE__ */ React.createElement(DropdownMenu.Arrow, null))));
87
- } finally {
88
- _effect.f();
89
- }
90
- };
91
-
92
- // src/components/Table/ColumnSettings.tsx
93
- import { useSignals as _useSignals2 } from "@preact-signals/safe-react/tracking";
94
- import React2, { useCallback, useEffect, useMemo, useState } from "react";
95
- import { Popover } from "@dxos/react-ui";
96
- import { FieldEditor } from "@dxos/react-ui-form";
97
- var ColumnSettings = ({ model, modals, onNewColumn }) => {
98
- var _effect = _useSignals2();
99
- try {
100
- const [newField, setNewField] = useState();
101
- const state = modals.state.value;
102
- const space = model?.space;
103
- useEffect(() => {
104
- if (state?.type === "columnSettings" && state.mode.type === "create" && model?.projection) {
105
- setNewField(model.projection.createFieldProjection());
106
- requestAnimationFrame(() => {
107
- onNewColumn();
108
- });
109
- } else {
110
- setNewField(void 0);
111
- }
112
- }, [
113
- model?.projection,
114
- state
115
- ]);
116
- const existingField = useMemo(() => {
117
- if (state?.type === "columnSettings") {
118
- const { mode } = state;
119
- if (mode.type === "edit") {
120
- return model?.view?.fields.find((f) => f.id === mode.fieldId);
121
- }
122
- }
123
- return void 0;
124
- }, [
125
- model?.view?.fields,
126
- state
127
- ]);
128
- const field = existingField ?? newField;
129
- const handleSave = useCallback(() => {
130
- modals.close();
131
- }, [
132
- modals
133
- ]);
134
- const handleCancel = useCallback(() => {
135
- if (state?.type === "columnSettings" && state.mode.type === "create" && newField) {
136
- model?.projection?.deleteFieldProjection(newField.id);
137
- }
138
- }, [
139
- model?.projection,
140
- state,
141
- newField
142
- ]);
143
- if (!model?.view || !model.projection || !field) {
144
- return null;
145
- }
146
- return /* @__PURE__ */ React2.createElement(Popover.Root, {
147
- modal: false,
148
- open: state?.type === "columnSettings"
149
- }, /* @__PURE__ */ React2.createElement(Popover.VirtualTrigger, {
150
- virtualRef: modals.trigger
151
- }), /* @__PURE__ */ React2.createElement(Popover.Portal, null, /* @__PURE__ */ React2.createElement(Popover.Content, {
152
- classNames: "md:is-64"
153
- }, /* @__PURE__ */ React2.createElement(Popover.Viewport, null, /* @__PURE__ */ React2.createElement(FieldEditor, {
154
- view: model.view,
155
- projection: model.projection,
156
- field,
157
- registry: space?.db.schemaRegistry,
158
- onSave: handleSave,
159
- onCancel: handleCancel
160
- })), /* @__PURE__ */ React2.createElement(Popover.Arrow, null))));
161
- } finally {
162
- _effect.f();
163
- }
164
- };
165
-
166
- // src/components/Table/CreateRefPanel.tsx
167
- import { useSignals as _useSignals3 } from "@preact-signals/safe-react/tracking";
168
- import { Schema } from "effect";
169
- import React3, { useCallback as useCallback2, useMemo as useMemo2 } from "react";
170
- import { live } from "@dxos/react-client/echo";
171
- import { Popover as Popover2 } from "@dxos/react-ui";
172
- import { Form } from "@dxos/react-ui-form";
173
- import { useGridContext } from "@dxos/react-ui-grid";
174
- var CreateRefPanel = ({ model, modals, __gridScope }) => {
175
- var _effect = _useSignals3();
176
- try {
177
- const { id: gridId } = useGridContext("TableCellEditor", __gridScope);
178
- const space = model?.space;
179
- const state = modals.state.value;
180
- const schema = useMemo2(() => {
181
- if (!space || state?.type !== "createRefPanel") {
182
- return;
183
- }
184
- const [schema2] = space.db.schemaRegistry.query({
185
- typename: state.typename
186
- }).runSync();
187
- if (schema2) {
188
- const omit = Schema.omit("id");
189
- return omit(schema2);
190
- }
191
- }, [
192
- space,
193
- state
194
- ]);
195
- const handleSave = useCallback2((values) => {
196
- if (!model || !space || state?.type !== "createRefPanel") {
197
- return;
198
- }
199
- const [schema2] = space.db.schemaRegistry.query({
200
- typename: state.typename
201
- }).runSync();
202
- if (schema2) {
203
- const obj = space.db.add(live(schema2, values));
204
- state.onCreate?.(obj);
205
- }
206
- void modals.close();
207
- }, [
208
- modals,
209
- space,
210
- state
211
- ]);
212
- const handleCancel = useCallback2(() => {
213
- if (model) {
214
- modals.close();
215
- }
216
- }, [
217
- modals
218
- ]);
219
- if (!model?.view || !model.projection) {
220
- return null;
221
- }
222
- return /* @__PURE__ */ React3.createElement(Popover2.Root, {
223
- modal: false,
224
- open: state?.type === "createRefPanel" && !!schema,
225
- onOpenChange: (nextOpen) => {
226
- if (model && !nextOpen) {
227
- return modals.close();
228
- }
229
- }
230
- }, /* @__PURE__ */ React3.createElement(Popover2.VirtualTrigger, {
231
- virtualRef: modals.trigger
232
- }), /* @__PURE__ */ React3.createElement(Popover2.Portal, null, /* @__PURE__ */ React3.createElement(Popover2.Content, {
233
- classNames: "md:is-64",
234
- "data-grid": gridId
235
- }, /* @__PURE__ */ React3.createElement(Popover2.Viewport, null, state?.type === "createRefPanel" && schema && /* @__PURE__ */ React3.createElement(Form, {
236
- schema,
237
- values: state.initialValues ?? {},
238
- onSave: handleSave,
239
- onCancel: handleCancel
240
- })), /* @__PURE__ */ React3.createElement(Popover2.Arrow, null))));
241
- } finally {
242
- _effect.f();
243
- }
244
- };
245
-
246
- // src/components/Table/RowActionsMenu.tsx
247
- import { useSignals as _useSignals4 } from "@preact-signals/safe-react/tracking";
248
- import React4 from "react";
249
- import { DropdownMenu as DropdownMenu2, useTranslation as useTranslation2 } from "@dxos/react-ui";
250
- var RowActionsMenu = ({ model, modals }) => {
251
- var _effect = _useSignals4();
252
- try {
253
- const { t } = useTranslation2(translationKey);
254
- const hasSelection = model.selection.hasSelection.value;
255
- const state = modals.state.value;
256
- if (state?.type !== "row") {
257
- return null;
258
- }
259
- return /* @__PURE__ */ React4.createElement(DropdownMenu2.Root, {
260
- modal: false,
261
- open: true,
262
- onOpenChange: modals.close
263
- }, /* @__PURE__ */ React4.createElement(DropdownMenu2.VirtualTrigger, {
264
- virtualRef: modals.trigger
265
- }), /* @__PURE__ */ React4.createElement(DropdownMenu2.Content, null, /* @__PURE__ */ React4.createElement(DropdownMenu2.Viewport, null, model.features.dataEditable !== false && /* @__PURE__ */ React4.createElement(DropdownMenu2.Item, {
266
- "data-testid": "row-menu-delete",
267
- onClick: () => model.deleteRow(state.rowIndex)
268
- }, t(hasSelection ? "bulk delete row label" : "delete row label")), model.rowActions?.map((action) => /* @__PURE__ */ React4.createElement(DropdownMenu2.Item, {
269
- key: action.id,
270
- "data-testid": `row-action-${action.id}`,
271
- onClick: () => {
272
- modals.close();
273
- model.handleRowAction(action.id, state.rowIndex);
274
- }
275
- }, t(action.translationKey)))), /* @__PURE__ */ React4.createElement(DropdownMenu2.Arrow, null)));
276
- } finally {
277
- _effect.f();
278
- }
279
- };
280
-
281
- // src/model/table-model.ts
282
- import { computed as computed3, effect as effect2, signal as signal3 } from "@preact/signals-core";
283
- import { Resource as Resource2 } from "@dxos/context";
284
- import { Ref as Ref3 } from "@dxos/echo";
285
- import { FormatEnum as FormatEnum3, getValue as getValue2, setValue, getSnapshot, getSchema, toEffectSchema } from "@dxos/echo-schema";
286
- import { invariant } from "@dxos/invariant";
287
- import { ObjectId } from "@dxos/keys";
288
- import { isLiveObject } from "@dxos/live-object";
289
- import { formatForEditing, parseValue } from "@dxos/react-ui-form";
290
- import { validateSchema } from "@dxos/schema";
291
-
292
- // src/model/selection-model.ts
293
- import { computed, effect, signal } from "@preact/signals-core";
294
- import { Resource } from "@dxos/context";
295
-
296
- // src/util/dynamic-table.ts
297
- import { Obj, Ref } from "@dxos/echo";
298
- import { getTypename, toJsonSchema } from "@dxos/echo-schema";
299
- import { createView, getSchemaFromPropertyDefinitions, ViewProjection } from "@dxos/schema";
300
- var getBaseSchema = ({ typename, properties, jsonSchema, schema }) => {
301
- if (typename && properties) {
302
- const schema2 = getSchemaFromPropertyDefinitions(typename, properties);
303
- return {
304
- typename: schema2.typename,
305
- jsonSchema: schema2.jsonSchema
306
- };
307
- } else if (schema) {
308
- return {
309
- typename: getTypename(schema),
310
- jsonSchema: toJsonSchema(schema)
311
- };
312
- } else if (typename && jsonSchema) {
313
- return {
314
- typename,
315
- jsonSchema
316
- };
317
- } else {
318
- throw new Error("invalid properties");
319
- }
320
- };
321
- var makeDynamicTable = ({ typename, jsonSchema, properties }) => {
322
- const view = createView({
323
- name: "dynamic-table",
324
- typename,
325
- jsonSchema,
326
- ...properties && {
327
- fields: properties.map((property) => property.name)
328
- }
329
- });
330
- const table = Obj.make(TableType, {
331
- name: "dynamic-table",
332
- view: Ref.make(view)
333
- });
334
- const projection = new ViewProjection(jsonSchema, view);
335
- if (properties && view.fields) {
336
- setProperties(view, projection, properties);
337
- }
338
- return {
339
- table,
340
- projection
341
- };
342
- };
343
- var setProperties = (view, projection, properties) => {
344
- for (const property of properties) {
345
- const field = view.fields.find((field2) => field2.path === property.name);
346
- if (field) {
347
- if (property.size !== void 0) {
348
- field.size = property.size;
349
- }
350
- if (property.title !== void 0) {
351
- const fieldProjection = projection.getFieldProjection(field.id);
352
- projection.setFieldProjection({
353
- ...fieldProjection,
354
- props: {
355
- ...fieldProjection.props,
356
- title: property.title
357
- }
358
- });
359
- }
360
- if (property.sort) {
361
- const fieldId = field.id;
362
- view.query.sort = [
363
- {
364
- fieldId,
365
- direction: property.sort
366
- }
367
- ];
368
- }
369
- }
370
- }
371
- };
372
-
373
- // src/util/signals.ts
374
- var touch = (value) => value;
375
-
376
- // src/util/table.ts
377
- import { Schema as Schema2 } from "effect";
378
- import { Format, FormatEnum, Ref as Ref2, TypedObject, TypeEnum } from "@dxos/echo-schema";
379
- import { PublicKey } from "@dxos/react-client";
380
- import { live as live2 } from "@dxos/react-client/echo";
381
- import { createFieldId, createView as createView2, getSchemaProperties, ViewProjection as ViewProjection2 } from "@dxos/schema";
382
- var initializeTable = async ({ client, space, table, typename, initialRow = true }) => {
383
- if (typename) {
384
- const schema = await space.db.graph.getSchemaByTypename(typename, space.db);
385
- if (!schema) {
386
- throw new Error(`Schema not found: ${typename}`);
387
- }
388
- const fields = getSchemaProperties(schema.ast).map((prop) => prop.name);
389
- table.view = Ref2.make(createView2({
390
- // TODO(ZaymonFC): Don't hardcode name?
391
- name: "View",
392
- typename: schema.typename,
393
- jsonSchema: schema.jsonSchema,
394
- fields
395
- }));
396
- return schema;
397
- } else {
398
- const [schema] = await space.db.schemaRegistry.register([
399
- createContactSchema()
400
- ]);
401
- const fields = ContactFields;
402
- table.view = Ref2.make(createView2({
403
- name: "View",
404
- typename: schema.typename,
405
- jsonSchema: schema.jsonSchema,
406
- fields
407
- }));
408
- createProjection(schema, table.view.target);
409
- if (initialRow) {
410
- space.db.add(live2(schema, {}));
411
- }
412
- return schema;
413
- }
414
- };
415
- var createContactSchema = () => TypedObject({
416
- typename: `example.com/type/${PublicKey.random().truncate()}`,
417
- version: "0.1.0"
418
- })({
419
- name: Schema2.optional(Schema2.String).annotations({
420
- title: "Name"
421
- }),
422
- active: Schema2.optional(Schema2.Boolean).annotations({
423
- title: "Active"
424
- }),
425
- email: Schema2.optional(Format.Email),
426
- salary: Schema2.optional(Format.Currency()).annotations({
427
- title: "Salary"
428
- })
429
- });
430
- var ContactFields = [
431
- "name",
432
- "email",
433
- "salary",
434
- "active"
435
- ];
436
- var createProjection = (schema, view) => {
437
- const projection = new ViewProjection2(schema.jsonSchema, view);
438
- projection.setFieldProjection({
439
- field: {
440
- id: view.fields.find((f) => f.path === "salary").id,
441
- path: "salary",
442
- size: 150
443
- }
444
- });
445
- projection.setFieldProjection({
446
- field: {
447
- id: view.fields.find((f) => f.path === "active").id,
448
- path: "active",
449
- size: 100
450
- }
451
- });
452
- projection.setFieldProjection({
453
- field: {
454
- id: createFieldId(),
455
- path: "manager",
456
- referencePath: "name"
457
- },
458
- props: {
459
- property: "manager",
460
- type: TypeEnum.Ref,
461
- format: FormatEnum.Ref,
462
- referenceSchema: schema.typename,
463
- title: "Manager"
464
- }
465
- });
466
- return projection;
467
- };
468
-
469
- // src/util/table-buttons.ts
470
- var BUTTON_IDENTIFIERS = {
471
- columnSettings: "data-table-column-settings-button",
472
- newColumn: "data-table-new-column-button",
473
- referencedCell: "data-table-ref-cell-button",
474
- rowMenu: "data-table-row-menu-button",
475
- saveDraftRow: "data-table-save-draft-row-button",
476
- sort: "data-table-sort-button"
477
- };
478
- var createButton = ({ attr, data, disabled = false, icon, testId, type = "primary" }) => {
479
- const dataAttrs = Object.entries(data).map(([k, v]) => `${k}="${v}"`).join(" ");
480
- const positionClass = type === "primary" ? "inline-end-2" : "inline-end-9";
481
- return `<button ${attr} data-testid="${testId}" class="dx-button is-6 pli-0.5 min-bs-0 absolute inset-block-1 ${positionClass}" ${dataAttrs} ${disabled ? "disabled" : ""}><svg data-size="4"><use href="/icons.svg#${icon}"/></svg></button>`;
482
- };
483
- var addColumnButton = {
484
- attr: BUTTON_IDENTIFIERS.newColumn,
485
- icon: "ph--plus--regular",
486
- render: ({ disabled }) => {
487
- return createButton({
488
- attr: BUTTON_IDENTIFIERS.newColumn,
489
- icon: addColumnButton.icon,
490
- disabled,
491
- data: {},
492
- testId: "table-new-column-button"
493
- });
494
- },
495
- getData: (_el) => ({
496
- type: "newColumn"
497
- })
498
- };
499
- var columnSettingsButton = {
500
- attr: BUTTON_IDENTIFIERS.columnSettings,
501
- icon: "ph--caret-down--regular",
502
- render: ({ fieldId }) => {
503
- return createButton({
504
- attr: BUTTON_IDENTIFIERS.columnSettings,
505
- icon: columnSettingsButton.icon,
506
- data: {
507
- "data-field-id": fieldId
508
- },
509
- testId: "table-column-settings-button"
510
- });
511
- },
512
- getData: (el) => ({
513
- type: "columnSettings",
514
- fieldId: el.getAttribute("data-field-id")
515
- })
516
- };
517
- var rowMenuButton = {
518
- attr: BUTTON_IDENTIFIERS.rowMenu,
519
- icon: "ph--dots-three--regular",
520
- render: ({ rowIndex }) => {
521
- return createButton({
522
- attr: BUTTON_IDENTIFIERS.rowMenu,
523
- icon: rowMenuButton.icon,
524
- data: {
525
- "data-row-index": rowIndex.toString()
526
- },
527
- testId: "table-row-menu-button"
528
- });
529
- },
530
- getData: (el) => ({
531
- type: "rowMenu",
532
- rowIndex: Number(el.getAttribute("data-row-index"))
533
- })
534
- };
535
- var sortButton = {
536
- attr: BUTTON_IDENTIFIERS.sort,
537
- icon: "ph--sort-ascending--regular",
538
- render: ({ fieldId, direction }) => {
539
- return createButton({
540
- attr: BUTTON_IDENTIFIERS.sort,
541
- icon: direction === "desc" ? "ph--sort-descending--regular" : "ph--sort-ascending--regular",
542
- data: {
543
- "data-field-id": fieldId,
544
- "data-direction": direction ?? ""
545
- },
546
- testId: "table-sort-button",
547
- // TODO(ZaymonFC): All buttons should be rendered into an absolutely positioned flex container.
548
- // This is a bit hacky.
549
- type: "secondary"
550
- });
551
- },
552
- getData: (el) => ({
553
- type: "sort",
554
- fieldId: el.getAttribute("data-field-id"),
555
- direction: el.getAttribute("data-direction")
556
- })
557
- };
558
- var saveDraftRowButton = {
559
- attr: BUTTON_IDENTIFIERS.saveDraftRow,
560
- icon: "ph--floppy-disk--regular",
561
- render: ({ rowIndex, disabled }) => {
562
- return createButton({
563
- attr: BUTTON_IDENTIFIERS.saveDraftRow,
564
- icon: saveDraftRowButton.icon,
565
- disabled,
566
- data: {
567
- "data-row-index": rowIndex.toString()
568
- },
569
- testId: "table-save-draft-row-button"
570
- });
571
- },
572
- getData: (el) => ({
573
- type: "saveDraftRow",
574
- rowIndex: Number(el.getAttribute("data-row-index"))
575
- })
576
- };
577
- var tableButtons = {
578
- addColumn: addColumnButton,
579
- columnSettings: columnSettingsButton,
580
- rowMenu: rowMenuButton,
581
- saveDraftRow: saveDraftRowButton,
582
- sort: sortButton
583
- };
584
-
585
- // src/util/table-controls.ts
586
- var CONTROL_IDENTIFIERS = {
587
- checkbox: "data-table-checkbox",
588
- switch: "data-table-switch"
589
- };
590
- var BASE_CLASSES = {
591
- checkbox: "absolute inset-block-[.375rem] inline-end-[.375rem] dx-checkbox",
592
- switch: "absolute inset-block-[.25rem] inline-end-[.25rem] dx-checkbox--switch"
593
- };
594
- var renderAttributes = (data) => {
595
- return Object.entries(data).map(([k, v]) => `${k}="${v}"`).join(" ");
596
- };
597
- var renderInput = (baseClass, attrs, checked = false, disabled = false, preventToggle = false, testId) => {
598
- return `<input type="checkbox" class="${baseClass}" ${renderAttributes(attrs)} ${checked ? "checked" : ""} ${preventToggle ? 'onclick="return false"' : ""} ${disabled ? "disabled" : ""} data-testid="${testId}"/>`;
599
- };
600
- var CheckboxStory = ({ rowIndex, header = false, checked = false, disabled = false }) => {
601
- const attrs = {
602
- [CONTROL_IDENTIFIERS.checkbox]: "",
603
- "data-row-index": rowIndex.toString(),
604
- ...header && {
605
- "data-header": ""
606
- }
607
- };
608
- return renderInput(BASE_CLASSES.checkbox, attrs, checked, disabled, true, "table-selection");
609
- };
610
- var SwitchStory = ({ colIndex, rowIndex, checked = false, disabled = false }) => {
611
- const attrs = {
612
- [CONTROL_IDENTIFIERS.switch]: "",
613
- "data-row-index": rowIndex.toString(),
614
- "data-col-index": colIndex.toString()
615
- };
616
- return renderInput(BASE_CLASSES.switch, attrs, checked, disabled, true, "table-switch");
617
- };
618
- var tableControls = {
619
- checkbox: {
620
- attr: CONTROL_IDENTIFIERS.checkbox,
621
- render: CheckboxStory,
622
- getData: (el) => ({
623
- type: "checkbox",
624
- rowIndex: Number(el.getAttribute("data-row-index")),
625
- header: el.hasAttribute("data-header")
626
- })
627
- },
628
- switch: {
629
- attr: CONTROL_IDENTIFIERS.switch,
630
- render: SwitchStory,
631
- getData: (el) => ({
632
- type: "switch",
633
- rowIndex: Number(el.getAttribute("data-row-index")),
634
- colIndex: Number(el.getAttribute("data-col-index"))
635
- })
636
- }
637
- };
638
-
639
- // src/util/schema.ts
640
- import { Schema as Schema3 } from "effect";
641
- import { isTypeLiteral, TypeLiteral } from "effect/SchemaAST";
642
- var narrowSchema = (schema, paths) => {
643
- const ast = schema?.ast;
644
- if (isTypeLiteral(ast)) {
645
- const propertySignatures = ast.propertySignatures.filter((signature) => paths.includes(signature.name.toString()));
646
- if (propertySignatures.length > 0) {
647
- const narrowType = new TypeLiteral(propertySignatures, []);
648
- return Schema3.make(narrowType);
649
- }
650
- }
651
- return void 0;
652
- };
653
-
654
- // src/model/selection-model.ts
655
- var SelectionModel = class extends Resource {
656
- constructor(_rows, _selectionMode, _onSelectionChanged) {
657
- super(), this._rows = _rows, this._selectionMode = _selectionMode, this._onSelectionChanged = _onSelectionChanged, this._selection = signal(/* @__PURE__ */ new Set()), this._validSelectedIds = computed(() => {
658
- const rows = this._rows.value;
659
- if (!rows) {
660
- return /* @__PURE__ */ new Set();
661
- }
662
- const validIds = new Set(rows.map((row) => row.id));
663
- return new Set([
664
- ...this._selection.value
665
- ].filter((id) => validIds.has(id)));
666
- }), this._hasSelection = computed(() => this._validSelectedIds.value.size > 0), this._allSelected = computed(() => {
667
- const rows = this._rows.value;
668
- if (rows.length === 0) {
669
- return false;
670
- }
671
- return rows.every((row) => this._selection.value.has(row.id));
672
- }), this.getSelectedRows = () => {
673
- const selectedIds = this._validSelectedIds.value;
674
- return this._rows.value.filter((row) => selectedIds.has(row.id));
675
- }, this.isRowIndexSelected = (rowIndex) => {
676
- const row = this._rows.value[rowIndex];
677
- return this._selection.value.has(row.id);
678
- }, this.isObjectSelected = (object) => {
679
- return this._selection.value.has(object.id);
680
- }, this.toggleSelectionForRowIndex = (rowIndex) => {
681
- const row = this._rows.value[rowIndex];
682
- const newSelection = new Set(this._selection.value);
683
- if (newSelection.has(row.id)) {
684
- newSelection.delete(row.id);
685
- } else {
686
- if (this._selectionMode === "single") {
687
- newSelection.clear();
688
- }
689
- newSelection.add(row.id);
690
- }
691
- this._selection.value = newSelection;
692
- }, this.setSelection = (mode) => {
693
- switch (mode) {
694
- case "all": {
695
- this._selection.value = new Set(this._rows.value.map((row) => row.id));
696
- break;
697
- }
698
- case "none": {
699
- this._selection.value = /* @__PURE__ */ new Set();
700
- break;
701
- }
702
- }
703
- };
704
- }
705
- async _open() {
706
- const selectionWatcher = effect(() => {
707
- touch(this._selection.value);
708
- this._onSelectionChanged?.();
709
- });
710
- this._ctx.onDispose(selectionWatcher);
711
- }
712
- //
713
- // Getters
714
- //
715
- get selection() {
716
- return this._selection;
717
- }
718
- get hasSelection() {
719
- return this._hasSelection;
720
- }
721
- get allRowsSeleted() {
722
- return this._allSelected;
723
- }
724
- get selectionMode() {
725
- return this._selectionMode;
726
- }
727
- };
728
-
729
- // src/model/table-sorting.ts
730
- import { computed as computed2, signal as signal2 } from "@preact/signals-core";
731
- import orderBy from "lodash.orderby";
732
- import { getValue, FormatEnum as FormatEnum2, TypeEnum as TypeEnum2 } from "@dxos/echo-schema";
733
- import { formatForDisplay } from "@dxos/react-ui-form";
734
- var TableSorting = class {
735
- constructor(rows, _view, _projection) {
736
- this._view = _view;
737
- this._projection = _projection;
738
- this._displayToDataIndex = /* @__PURE__ */ new Map();
739
- this._localSort = signal2(void 0);
740
- this._rows = rows;
741
- this._isDirty = computed2(() => {
742
- const local = this._localSort.value;
743
- const viewSort = this._view?.query.sort?.[0];
744
- if (local?.type === "cleared") {
745
- return viewSort !== void 0;
746
- }
747
- if (local?.type === "active") {
748
- if (!viewSort) {
749
- return true;
750
- }
751
- return local.sort.fieldId !== viewSort.fieldId || local.sort.direction !== viewSort.direction;
752
- }
753
- return false;
754
- });
755
- this.sortedRows = this.initialiseSortedRows();
756
- }
757
- /**
758
- * @reactive
759
- * @returns Local sort if present, falls back to view sort.
760
- */
761
- get sorting() {
762
- const local = this._localSort.value;
763
- if (local?.type === "cleared") {
764
- return void 0;
765
- }
766
- if (local?.type === "active") {
767
- return local.sort;
768
- }
769
- return this._view?.query.sort?.[0];
770
- }
771
- /**
772
- * @reactive
773
- * @returns Whether local sort differs from view sort.
774
- */
775
- get isDirty() {
776
- return this._isDirty.value;
777
- }
778
- getDataIndex(displayIndex) {
779
- return this._displayToDataIndex.get(displayIndex) ?? displayIndex;
780
- }
781
- setSort(fieldId, direction) {
782
- this._localSort.value = {
783
- type: "active",
784
- sort: {
785
- fieldId,
786
- direction
787
- }
788
- };
789
- }
790
- toggleSort(fieldId) {
791
- if (!this._view || !this.sorting || this.sorting.fieldId !== fieldId) {
792
- return;
793
- }
794
- switch (this.sorting.direction) {
795
- case "asc": {
796
- this.setSort(fieldId, "desc");
797
- break;
798
- }
799
- case "desc": {
800
- this.setSort(fieldId, "asc");
801
- break;
802
- }
803
- }
804
- }
805
- clearSort() {
806
- this._localSort.value = {
807
- type: "cleared"
808
- };
809
- }
810
- save() {
811
- if (this._view && this._localSort.value !== void 0) {
812
- if (this._localSort.value.type === "active") {
813
- this._view.query.sort = [
814
- this._localSort.value.sort
815
- ];
816
- } else {
817
- this._view.query.sort = [];
818
- }
819
- this._localSort.value = void 0;
820
- }
821
- }
822
- //
823
- // Initialisation.
824
- //
825
- initialiseSortedRows() {
826
- return computed2(() => {
827
- this._displayToDataIndex.clear();
828
- const sort = this.sorting;
829
- if (!sort || !this._view) {
830
- return this._rows.value;
831
- }
832
- const field = this._view.fields.find((f) => f.id === sort.fieldId);
833
- if (!field) {
834
- return this._rows.value;
835
- }
836
- const { props } = this._projection.getFieldProjection(field.id);
837
- const dataWithIndices = this._rows.value.map((item, index) => {
838
- const sortValue = this.getSortValue(props, field, item);
839
- const isEmpty = !(props.type === TypeEnum2.Boolean) && (sortValue == null || sortValue === "");
840
- return {
841
- item,
842
- index,
843
- value: sortValue,
844
- isEmpty
845
- };
846
- });
847
- const ordered = orderBy(dataWithIndices, [
848
- "isEmpty",
849
- "value"
850
- ], [
851
- "asc",
852
- sort.direction
853
- ]);
854
- ordered.forEach(({ index: dataIndex }, displayIndex) => {
855
- if (displayIndex !== dataIndex) {
856
- this._displayToDataIndex.set(displayIndex, dataIndex);
857
- }
858
- });
859
- return ordered.map(({ item }) => item);
860
- });
861
- }
862
- getSortValue(props, field, item) {
863
- const { type, format } = props;
864
- const value = getValue(item, field.path);
865
- if (type === TypeEnum2.Boolean) {
866
- return !!value;
867
- }
868
- if (type === TypeEnum2.Number) {
869
- return value;
870
- }
871
- if (value == null) {
872
- return value;
873
- }
874
- if (format === FormatEnum2.SingleSelect) {
875
- const options = props.options;
876
- if (!options) {
877
- return void 0;
878
- }
879
- const option = options.find((option2) => option2.id === value);
880
- return option ? options.indexOf(option) : void 0;
881
- }
882
- if (format === FormatEnum2.Ref && field.referencePath) {
883
- const refValue = getValue(value.target, field.referencePath);
884
- return refValue;
885
- }
886
- return formatForDisplay({
887
- type,
888
- format,
889
- value
890
- });
891
- }
892
- };
893
-
894
- // src/util/tag.ts
895
- var TAG_PATTERN_REGEX = /\[([^\]]+)\]\(([^)]+)\)/g;
896
- var extractTagIds = (value) => {
897
- if (typeof value !== "string") {
898
- return void 0;
899
- }
900
- const ids = [];
901
- let match;
902
- while ((match = TAG_PATTERN_REGEX.exec(value)) !== null) {
903
- if (match[2]) {
904
- ids.push(match[2]);
905
- }
906
- }
907
- return ids.length > 0 ? ids : void 0;
908
- };
909
-
910
- // src/model/table-model.ts
911
- var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-table/src/model/table-model.ts";
912
- var defaultFeatures = {
913
- selection: {
914
- enabled: true,
915
- mode: "multiple"
916
- },
917
- dataEditable: false,
918
- schemaEditable: false
919
- };
920
- var TableModel = class extends Resource2 {
921
- constructor({ id, space, view, projection, features = {}, sorting = [], pinnedRows = {
922
- top: [],
923
- bottom: []
924
- }, rowActions = [], onCellUpdate, onDeleteColumn, onDeleteRows, onInsertRow, onRowOrderChange, onRowAction }) {
925
- super();
926
- this._visibleRange = signal3({
927
- start: {
928
- row: 0,
929
- col: 0
930
- },
931
- end: {
932
- row: 0,
933
- col: 0
934
- }
935
- });
936
- this._rows = signal3([]);
937
- this._draftRows = signal3([]);
938
- //
939
- // Data
940
- //
941
- this.setRows = (obj) => {
942
- this._rows.value = obj;
943
- };
944
- this.getRowCount = () => this._rows.value.length;
945
- /**
946
- * @reactive
947
- */
948
- this.getDraftRowCount = () => this._draftRows.value.length;
949
- this.getColumnCount = () => this._view?.fields.length ?? 0;
950
- this.insertRow = () => {
951
- const result = this._onInsertRow?.();
952
- if (result === false && this._draftRows.value.length === 0) {
953
- this.createDraftRow();
954
- }
955
- };
956
- this.commitDraftRow = (draftRowIndex) => {
957
- if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.value.length) {
958
- return false;
959
- }
960
- const draftRow = this._draftRows.value[draftRowIndex];
961
- if (!draftRow.valid) {
962
- return false;
963
- }
964
- const success = this._onInsertRow?.(draftRow.data);
965
- if (success) {
966
- const newDraftRows = this._draftRows.value.filter((_, index) => index !== draftRowIndex);
967
- this._draftRows.value = newDraftRows;
968
- }
969
- return success ?? false;
970
- };
971
- this.deleteRow = (rowIndex) => {
972
- const row = this._sorting.getDataIndex(rowIndex);
973
- const obj = this._rows.value[row];
974
- const objectsToDelete = [];
975
- if (this.features.selection.enabled && this._selection.hasSelection.value) {
976
- const selectedRows = this._selection.getSelectedRows();
977
- objectsToDelete.push(...selectedRows);
978
- } else {
979
- objectsToDelete.push(obj);
980
- }
981
- this._onDeleteRows?.(row, objectsToDelete);
982
- };
983
- this.handleRowAction = (actionId, rowIndex) => {
984
- if (!this._onRowAction) {
985
- return;
986
- }
987
- const row = this._sorting.getDataIndex(rowIndex);
988
- const data = this._rows.value[row];
989
- if (data) {
990
- this._onRowAction(actionId, data);
991
- }
992
- };
993
- this.getCellData = (cell) => {
994
- const { col, row, plane } = cell;
995
- const fields = this._view?.fields ?? [];
996
- if (col < 0 || col >= fields.length) {
997
- return void 0;
998
- }
999
- const field = fields[col];
1000
- let value;
1001
- if (plane === "frozenRowsEnd") {
1002
- if (row >= 0 && row < this._draftRows.value.length) {
1003
- value = getValue2(this._draftRows.value[row].data, field.path);
1004
- } else {
1005
- return void 0;
1006
- }
1007
- } else {
1008
- const dataIndex = this._sorting.getDataIndex(row);
1009
- value = getValue2(this._rows.value[dataIndex], field.path);
1010
- }
1011
- if (value == null) {
1012
- return "";
1013
- }
1014
- const { props } = this._projection.getFieldProjection(field.id);
1015
- switch (props.format) {
1016
- case FormatEnum3.Ref: {
1017
- if (!field.referencePath) {
1018
- return "";
1019
- }
1020
- return getValue2(value.target, field.referencePath);
1021
- }
1022
- default: {
1023
- return formatForEditing({
1024
- type: props.type,
1025
- format: props.format,
1026
- value
1027
- });
1028
- }
1029
- }
1030
- };
1031
- this.validateCellData = async ({ col, row, plane }, value) => {
1032
- const fields = this._view?.fields ?? [];
1033
- if (col < 0 || col >= fields.length) {
1034
- return {
1035
- valid: false,
1036
- error: "Invalid column index"
1037
- };
1038
- }
1039
- const field = fields[col];
1040
- const { props } = this._projection.getFieldProjection(field.id);
1041
- const transformedValue = editorTextToCellValue(props, value);
1042
- const isDraftRow = plane === "frozenRowsEnd";
1043
- if (isDraftRow) {
1044
- const isInvalidDraftRowIndex = row < 0 || row >= this._draftRows.value.length;
1045
- if (isInvalidDraftRowIndex) {
1046
- return {
1047
- valid: false,
1048
- error: "Invalid draft row index"
1049
- };
1050
- }
1051
- const draftRow = this._draftRows.value[row];
1052
- const snapshot = {
1053
- ...draftRow.data
1054
- };
1055
- setValue(snapshot, field.path, transformedValue);
1056
- const validationErrors = this.validateDraftRowData(snapshot);
1057
- if (validationErrors.length > 0) {
1058
- const error = validationErrors.find((err) => err.path === field.path);
1059
- if (error) {
1060
- return {
1061
- valid: false,
1062
- error: error.message
1063
- };
1064
- }
1065
- }
1066
- return {
1067
- valid: true
1068
- };
1069
- } else {
1070
- const rowIdx = this._sorting.getDataIndex(row);
1071
- const currentRow = this._rows.value[rowIdx];
1072
- invariant(currentRow, "Invalid row index", {
1073
- F: __dxlog_file,
1074
- L: 507,
1075
- S: this,
1076
- A: [
1077
- "currentRow",
1078
- "'Invalid row index'"
1079
- ]
1080
- });
1081
- const snapshot = getSnapshot(currentRow);
1082
- setValue(snapshot, field.path, transformedValue);
1083
- const schema = getSchema(currentRow);
1084
- invariant(schema, void 0, {
1085
- F: __dxlog_file,
1086
- L: 513,
1087
- S: this,
1088
- A: [
1089
- "schema",
1090
- ""
1091
- ]
1092
- });
1093
- const validationResult = validateSchema(schema, snapshot);
1094
- if (validationResult && validationResult.length > 0) {
1095
- const error = validationResult[0];
1096
- invariant(error.path === field.path, void 0, {
1097
- F: __dxlog_file,
1098
- L: 518,
1099
- S: this,
1100
- A: [
1101
- "error.path === field.path",
1102
- ""
1103
- ]
1104
- });
1105
- return {
1106
- valid: false,
1107
- error: error.message
1108
- };
1109
- }
1110
- return {
1111
- valid: true
1112
- };
1113
- }
1114
- };
1115
- this.setCellData = (cell, value) => {
1116
- const { col, row, plane } = cell;
1117
- const fields = this._view?.fields ?? [];
1118
- if (col < 0 || col >= fields.length) {
1119
- return;
1120
- }
1121
- const field = fields[col];
1122
- const { props } = this._projection.getFieldProjection(field.id);
1123
- const transformedValue = editorTextToCellValue(props, value);
1124
- if (props.format === FormatEnum3.Ref && !isLiveObject(value)) {
1125
- return;
1126
- }
1127
- if (plane === "frozenRowsEnd") {
1128
- if (row >= 0 && row < this._draftRows.value.length) {
1129
- setValue(this._draftRows.value[row].data, field.path, transformedValue);
1130
- this.validateDraftRow(row);
1131
- }
1132
- } else {
1133
- const rowIdx = this._sorting.getDataIndex(row);
1134
- setValue(this._rows.value[rowIdx], field.path, transformedValue);
1135
- }
1136
- };
1137
- this._id = id;
1138
- this._space = space;
1139
- this._view = view;
1140
- this._projection = projection;
1141
- this._features = {
1142
- ...defaultFeatures,
1143
- ...features
1144
- };
1145
- invariant(!(this._features.dataEditable && this._features.selection.enabled && this._features.selection.mode === "single"), "Single selection is not compatible with editable tables.", {
1146
- F: __dxlog_file,
1147
- L: 145,
1148
- S: this,
1149
- A: [
1150
- "!(this._features.dataEditable && this._features.selection.enabled && this._features.selection.mode === 'single')",
1151
- "'Single selection is not compatible with editable tables.'"
1152
- ]
1153
- });
1154
- this._sorting = new TableSorting(this._rows, this._view, projection);
1155
- if (sorting.length > 0) {
1156
- const [sort] = sorting;
1157
- this._sorting.setSort(sort.fieldId, sort.direction);
1158
- }
1159
- this._pinnedRows = pinnedRows;
1160
- this._rowActions = rowActions;
1161
- this._onInsertRow = onInsertRow;
1162
- this._onDeleteRows = onDeleteRows;
1163
- this._onDeleteColumn = onDeleteColumn;
1164
- this._onCellUpdate = onCellUpdate;
1165
- this._onRowOrderChange = onRowOrderChange;
1166
- this._onRowAction = onRowAction;
1167
- }
1168
- get id() {
1169
- return this._id;
1170
- }
1171
- get space() {
1172
- return this._space;
1173
- }
1174
- get view() {
1175
- return this._view;
1176
- }
1177
- get projection() {
1178
- return this._projection;
1179
- }
1180
- get rows() {
1181
- return this._sorting.sortedRows;
1182
- }
1183
- get features() {
1184
- return this._features;
1185
- }
1186
- /**
1187
- * @reactive
1188
- */
1189
- get sorting() {
1190
- return this._sorting;
1191
- }
1192
- /**
1193
- * Gets the row data at the specified display index.
1194
- */
1195
- getRowAt(displayIndex) {
1196
- if (displayIndex < 0 || displayIndex >= this._sorting.sortedRows.value.length) {
1197
- return void 0;
1198
- }
1199
- return this._sorting.sortedRows.value[displayIndex];
1200
- }
1201
- get pinnedRows() {
1202
- return this._pinnedRows;
1203
- }
1204
- get columnMeta() {
1205
- invariant(this._columnMeta, void 0, {
1206
- F: __dxlog_file,
1207
- L: 214,
1208
- S: this,
1209
- A: [
1210
- "this._columnMeta",
1211
- ""
1212
- ]
1213
- });
1214
- return this._columnMeta;
1215
- }
1216
- get selection() {
1217
- return this._selection;
1218
- }
1219
- /** @reactive */
1220
- get isViewDirty() {
1221
- return this._sorting.isDirty;
1222
- }
1223
- get rowActions() {
1224
- return this._rowActions;
1225
- }
1226
- //
1227
- // Initialisation
1228
- //
1229
- async _open() {
1230
- this.initializeColumnMeta();
1231
- this.initializeEffects();
1232
- this._selection = new SelectionModel(this._sorting.sortedRows, this._features.selection.mode ?? "multiple", () => this._onRowOrderChange?.());
1233
- await this._selection.open(this._ctx);
1234
- }
1235
- initializeColumnMeta() {
1236
- this._columnMeta = computed3(() => {
1237
- const fields = this._view?.fields ?? [];
1238
- const meta = Object.fromEntries(fields.map((field, index) => [
1239
- index,
1240
- {
1241
- size: field?.size ?? 256,
1242
- resizeable: true
1243
- }
1244
- ]));
1245
- return {
1246
- grid: meta,
1247
- frozenColsStart: {
1248
- 0: {
1249
- size: 30,
1250
- resizeable: false
1251
- }
1252
- },
1253
- frozenColsEnd: {
1254
- 0: {
1255
- size: 40,
1256
- resizeable: false
1257
- }
1258
- }
1259
- };
1260
- });
1261
- }
1262
- initializeEffects() {
1263
- const rowOrderWatcher = effect2(() => {
1264
- touch(this._sorting.sortedRows.value);
1265
- this._onRowOrderChange?.();
1266
- });
1267
- this._ctx.onDispose(rowOrderWatcher);
1268
- const rowEffectManager = effect2(() => {
1269
- const { start, end } = touch(this._visibleRange.value);
1270
- const rowEffects = [];
1271
- for (let row = start.row; row <= end.row; row++) {
1272
- rowEffects.push(effect2(() => {
1273
- const obj = this._sorting.sortedRows.value[row];
1274
- this._view?.fields.forEach((field) => touch(getValue2(obj, field.path)));
1275
- this._onCellUpdate?.({
1276
- row,
1277
- col: start.col,
1278
- plane: "grid"
1279
- });
1280
- }));
1281
- }
1282
- return () => rowEffects.forEach((cleanup) => cleanup());
1283
- });
1284
- this._ctx.onDispose(rowEffectManager);
1285
- const draftRowsWatcher = effect2(() => {
1286
- touch(this._draftRows.value);
1287
- this._onRowOrderChange?.();
1288
- });
1289
- this._ctx.onDispose(draftRowsWatcher);
1290
- }
1291
- // TODO(ZaymonFC): Return .value instead of exposing the signal.
1292
- get draftRows() {
1293
- return this._draftRows;
1294
- }
1295
- /**
1296
- * Checks if a specific field path has validation errors for a given draft row.
1297
- * @param draftRowIndex - The index of the draft row to check
1298
- * @param fieldPath - The path of the field to check for validation errors
1299
- * @returns true if the field has validation errors, false otherwise
1300
- */
1301
- hasDraftRowValidationError(draftRowIndex, fieldPath) {
1302
- if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.value.length) {
1303
- return false;
1304
- }
1305
- const draftRow = this._draftRows.value[draftRowIndex];
1306
- const validationErrors = draftRow.validationErrors || [];
1307
- return validationErrors.some((error) => error.path === fieldPath);
1308
- }
1309
- createDraftRow() {
1310
- if (!this._view) {
1311
- return;
1312
- }
1313
- const draftData = {
1314
- id: ObjectId.random()
1315
- };
1316
- const validationErrors = this.validateDraftRowData(draftData);
1317
- const draftRow = {
1318
- data: draftData,
1319
- valid: validationErrors.length === 0,
1320
- validationErrors
1321
- };
1322
- this._draftRows.value = [
1323
- ...this._draftRows.value,
1324
- draftRow
1325
- ];
1326
- }
1327
- validateDraftRow(draftRowIndex) {
1328
- if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.value.length) {
1329
- return;
1330
- }
1331
- const draftRow = this._draftRows.value[draftRowIndex];
1332
- const validationErrors = this.validateDraftRowData(draftRow.data);
1333
- const updatedDraftRow = {
1334
- ...draftRow,
1335
- valid: validationErrors.length === 0,
1336
- validationErrors
1337
- };
1338
- const newDraftRows = [
1339
- ...this._draftRows.value
1340
- ];
1341
- newDraftRows[draftRowIndex] = updatedDraftRow;
1342
- this._draftRows.value = newDraftRows;
1343
- }
1344
- validateDraftRowData(data) {
1345
- const schema = toEffectSchema(this._projection.schema);
1346
- return validateSchema(schema, data) || [];
1347
- }
1348
- /**
1349
- * Updates a cell's value using a transform function.
1350
- * @param {DxGridPlanePosition} position - The position of the cell to update.
1351
- * @param {(value: any) => any} update - A function that takes the current value and returns the updated value.
1352
- */
1353
- updateCellData({ col, row }, update) {
1354
- const dataRow = this._sorting.getDataIndex(row);
1355
- const fields = this._view?.fields ?? [];
1356
- const field = fields[col];
1357
- const value = getValue2(this._rows.value[dataRow], field.path);
1358
- const updatedValue = update(value);
1359
- setValue(this._rows.value[dataRow], field.path, updatedValue);
1360
- }
1361
- /**
1362
- * Gets the domain-appropriate cell type for a given cell position.
1363
- * This abstracts away the low-level grid plane concepts and provides domain-specific types.
1364
- * @param cell - The cell position to classify
1365
- * @returns The domain-appropriate cell type
1366
- */
1367
- getCellType(cell) {
1368
- switch (cell.plane) {
1369
- case "frozenRowsEnd":
1370
- return "draft";
1371
- case "frozenRowsStart":
1372
- return "header";
1373
- case "grid":
1374
- default:
1375
- return "standard";
1376
- }
1377
- }
1378
- /**
1379
- * Convenience method to check if a cell is a draft cell.
1380
- * @param cell - The cell position to check
1381
- * @returns true if the cell is a draft cell, false otherwise
1382
- */
1383
- isDraftCell(cell) {
1384
- return this.getCellType(cell) === "draft";
1385
- }
1386
- //
1387
- // Column Operations
1388
- //
1389
- deleteColumn(fieldId) {
1390
- if (!this._view) {
1391
- return;
1392
- }
1393
- const field = this._view?.fields.find((field2) => field2.id === fieldId);
1394
- if (field && this._onDeleteColumn) {
1395
- this._onDeleteColumn(field.id);
1396
- }
1397
- }
1398
- //
1399
- // Resize
1400
- //
1401
- setColumnWidth(columnIndex, width) {
1402
- const fields = this._view?.fields ?? [];
1403
- if (columnIndex < fields.length) {
1404
- const newWidth = Math.max(0, width);
1405
- const field = fields[columnIndex];
1406
- if (field) {
1407
- field.size = newWidth;
1408
- }
1409
- }
1410
- }
1411
- //
1412
- // Pinning
1413
- //
1414
- // TODO(burdon): Change to setPinned(on/off).
1415
- pinRow(rowIndex, side) {
1416
- this._pinnedRows[side].push(rowIndex);
1417
- }
1418
- unpinRow(rowIndex) {
1419
- this._pinnedRows.top = this._pinnedRows.top.filter((index) => index !== rowIndex);
1420
- this._pinnedRows.bottom = this._pinnedRows.bottom.filter((index) => index !== rowIndex);
1421
- }
1422
- //
1423
- // View operations
1424
- //
1425
- saveView() {
1426
- this._sorting.save();
1427
- }
1428
- };
1429
- var editorTextToCellValue = (props, value) => {
1430
- switch (props.format) {
1431
- case FormatEnum3.Ref: {
1432
- if (isLiveObject(value)) {
1433
- return Ref3.make(value);
1434
- } else {
1435
- return value;
1436
- }
1437
- }
1438
- case FormatEnum3.SingleSelect: {
1439
- const ids = extractTagIds(value);
1440
- if (ids && ids.length > 0) {
1441
- return ids[0];
1442
- } else {
1443
- return value;
36
+ "create stack section label": "Create table",
37
+ "add row label": "Add row",
38
+ "create comment": "Create thread",
39
+ "column action sort descending": "Sort Descending",
40
+ "column action sort ascending": "Sort Ascending",
41
+ "column action clear sorting": "Clear Sorting",
42
+ "column action settings": "Column Settings",
43
+ "column action delete": "Delete Column",
44
+ "delete row label": "Delete row",
45
+ "bulk delete row label": "Delete selected rows",
46
+ "column deleted label": "Column deleted",
47
+ "new column button label": "Create column",
48
+ "create new object label": "Create new object \u201C{{text}}\u201D",
49
+ "save view label": "Save changes to view"
1444
50
  }
1445
51
  }
1446
- case FormatEnum3.MultiSelect: {
1447
- const ids = extractTagIds(value);
1448
- return ids || value;
1449
- }
1450
- default: {
1451
- return parseValue({
1452
- type: props.type,
1453
- format: props.format,
1454
- value
1455
- });
1456
- }
1457
- }
1458
- };
1459
-
1460
- // src/model/modal-controller.ts
1461
- import { signal as signal4 } from "@preact/signals-core";
1462
- import { log } from "@dxos/log";
1463
- var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/react-ui-table/src/model/modal-controller.ts";
1464
- var ModalController = class {
1465
- constructor() {
1466
- this._state = signal4({
1467
- type: "closed"
1468
- });
1469
- this._triggerRef = {
1470
- current: null
1471
- };
1472
- this.setTrigger = (trigger) => {
1473
- this._triggerRef.current = trigger;
1474
- };
1475
- this.showRowMenu = (rowIndex) => {
1476
- this._state.value = {
1477
- type: "row",
1478
- rowIndex
1479
- };
1480
- };
1481
- this.showColumnMenu = (fieldId) => {
1482
- this._state.value = {
1483
- type: "column",
1484
- fieldId
1485
- };
1486
- };
1487
- this.showColumnCreator = () => {
1488
- this._state.value = {
1489
- type: "columnSettings",
1490
- mode: {
1491
- type: "create"
1492
- }
1493
- };
1494
- };
1495
- this.showReferencePanel = (targetId, schemaId) => {
1496
- this._state.value = {
1497
- type: "refPanel",
1498
- targetId,
1499
- typename: schemaId
1500
- };
1501
- };
1502
- this.openColumnSettings = () => {
1503
- if (this._state.value.type === "column") {
1504
- const fieldId = this._state.value.fieldId;
1505
- requestAnimationFrame(() => {
1506
- this._state.value = {
1507
- type: "columnSettings",
1508
- mode: {
1509
- type: "edit",
1510
- fieldId
1511
- }
1512
- };
1513
- });
1514
- }
1515
- };
1516
- this.openCreateRef = (typename, anchorCell, initialValues, onCreate) => {
1517
- if (anchorCell) {
1518
- this._triggerRef.current = anchorCell;
1519
- this._state.value = {
1520
- type: "createRefPanel",
1521
- typename,
1522
- initialValues,
1523
- onCreate
1524
- };
1525
- } else {
1526
- log.warn("anchor cell not found while creating new ref", void 0, {
1527
- F: __dxlog_file2,
1528
- L: 86,
1529
- S: this,
1530
- C: (f, a) => f(...a)
1531
- });
1532
- }
1533
- };
1534
- this.close = () => {
1535
- this._state.value = {
1536
- type: "closed"
1537
- };
1538
- };
1539
- }
1540
- get state() {
1541
- return this._state;
1542
52
  }
1543
- get trigger() {
1544
- return this._triggerRef;
1545
- }
1546
- };
53
+ ];
1547
54
 
1548
- // src/model/table-presentation.ts
1549
- import { signal as signal5 } from "@preact/signals-core";
1550
- import { isNotNullable } from "effect/Predicate";
1551
- import { Obj as Obj2 } from "@dxos/echo";
1552
- import { FormatEnum as FormatEnum4, getValue as getValue3, TypeEnum as TypeEnum3 } from "@dxos/echo-schema";
1553
- import { cellClassesForFieldType, formatForDisplay as formatForDisplay2 } from "@dxos/react-ui-form";
1554
- import { toPlaneCellIndex } from "@dxos/react-ui-grid";
55
+ // src/components/Table/Table.tsx
56
+ import { useSignals as _useSignals8 } from "@preact-signals/safe-react/tracking";
57
+ import React8, { forwardRef, useCallback as useCallback5, useEffect as useEffect4, useImperativeHandle, useMemo as useMemo5, useState as useState4 } from "react";
58
+ import { Filter } from "@dxos/echo";
59
+ import { getValue } from "@dxos/echo-schema";
60
+ import { invariant as invariant3 } from "@dxos/invariant";
61
+ import { getSpace as getSpace2 } from "@dxos/react-client/echo";
62
+ import { useTranslation as useTranslation3 } from "@dxos/react-ui";
63
+ import { useAttention } from "@dxos/react-ui-attention";
64
+ import { Grid, closestCell, gridSeparatorBlockEnd, gridSeparatorInlineEnd } from "@dxos/react-ui-grid";
1555
65
  import { mx } from "@dxos/react-ui-theme";
1556
- import { VIEW_FIELD_LIMIT } from "@dxos/schema";
1557
- var TablePresentation = class {
1558
- constructor(model, _visibleRange = signal5({
1559
- start: {
1560
- row: 0,
1561
- col: 0
1562
- },
1563
- end: {
1564
- row: 0,
1565
- col: 0
1566
- }
1567
- })) {
1568
- this.model = model;
1569
- this._visibleRange = _visibleRange;
1570
- this.fieldProjectionCache = /* @__PURE__ */ new Map();
1571
- }
1572
- getCells(range, plane) {
1573
- this.fieldProjectionCache.clear();
1574
- let cells;
1575
- switch (plane) {
1576
- case "grid":
1577
- this._visibleRange.value = range;
1578
- cells = this.getMainGridCells(range);
1579
- break;
1580
- case "frozenRowsStart":
1581
- cells = this.getHeaderCells(range);
1582
- break;
1583
- case "frozenColsStart":
1584
- cells = this.getSelectionColumnCells(range);
1585
- break;
1586
- case "frozenColsEnd":
1587
- cells = this.getActionColumnCells(range);
1588
- break;
1589
- case "fixedStartStart":
1590
- cells = this.getSelectAllCell();
1591
- break;
1592
- case "fixedStartEnd":
1593
- cells = this.getNewColumnCell();
1594
- break;
1595
- case "frozenRowsEnd":
1596
- cells = this.getDraftRowCells(range);
1597
- break;
1598
- case "fixedEndEnd":
1599
- cells = this.getDraftActionCells(range);
1600
- break;
1601
- default:
1602
- cells = {};
1603
- }
1604
- if (plane === "grid" && this.model.features.dataEditable === false) {
1605
- Object.values(cells).forEach((cell) => {
1606
- cell.readonly = "text-select";
1607
- });
1608
- }
1609
- return cells;
1610
- }
1611
- /**
1612
- * Gets a field projection, using cache if available within the current render pass.
1613
- */
1614
- getFieldProjection(fieldId) {
1615
- let projection = this.fieldProjectionCache.get(fieldId);
1616
- if (!projection) {
1617
- projection = this.model.projection.getFieldProjection(fieldId);
1618
- this.fieldProjectionCache.set(fieldId, projection);
1619
- }
1620
- return projection;
1621
- }
1622
- createDataCell(cells, obj, field, colIndex, displayIndex) {
1623
- const { props } = this.getFieldProjection(field.id);
1624
- const cell = {
1625
- get value() {
1626
- const value = getValue3(obj, field.path);
1627
- if (value == null) {
1628
- return "";
1629
- }
1630
- if (props.type === "array") {
1631
- return "";
1632
- }
1633
- switch (props.format) {
1634
- case FormatEnum4.Boolean:
1635
- case FormatEnum4.SingleSelect:
1636
- case FormatEnum4.MultiSelect: {
1637
- return "";
1638
- }
1639
- case FormatEnum4.Ref: {
1640
- if (!field.referencePath) {
1641
- return "";
1642
- }
1643
- return getValue3(value.target, field.referencePath);
1644
- }
1645
- default: {
1646
- return formatForDisplay2({
1647
- type: props.type,
1648
- format: props.format,
1649
- value
1650
- });
1651
- }
1652
- }
1653
- }
1654
- };
1655
- const classes = [];
1656
- const formatClasses = cellClassesForFieldType({
1657
- type: props.type,
1658
- format: props.format
1659
- });
1660
- if (formatClasses) {
1661
- classes.push(formatClasses);
1662
- }
1663
- const rowSelectionClasses = cellClassesForRowSelection(this.model.selection.isObjectSelected(obj), this.model.selection.selectionMode);
1664
- if (rowSelectionClasses) {
1665
- classes.push(rowSelectionClasses);
1666
- }
1667
- if (classes.length > 0) {
1668
- cell.className = mx(classes.flat());
1669
- }
1670
- if (props.type === TypeEnum3.Array) {
1671
- const targetArray = getValue3(obj, field.path);
1672
- if (targetArray && Array.isArray(targetArray)) {
1673
- const getLabel = (value) => {
1674
- if (!value) {
1675
- return void 0;
1676
- }
1677
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1678
- return String(value);
1679
- }
1680
- if (Array.isArray(value)) {
1681
- return "Array";
1682
- }
1683
- if (typeof value === "object" && value.name) {
1684
- return value.name;
1685
- }
1686
- if (typeof value === "object" && value.value) {
1687
- return `${value.value}`;
1688
- }
1689
- return "Object";
1690
- };
1691
- const tags = targetArray.map(getLabel).filter(isNotNullable).map((title) => {
1692
- return `<span class="dx-tag rounded-xl" data-hue="neutral">${title}</span>`;
1693
- }).join("");
1694
- cell.accessoryHtml = `<div role='none' class="flex flex-row gap-1 overflow-x-auto">${tags}</div>`;
1695
- }
1696
- }
1697
- if (props.format === FormatEnum4.Ref && props.referenceSchema) {
1698
- const targetObj = getValue3(obj, field.path)?.target;
1699
- if (targetObj) {
1700
- const dxn = Obj2.getDXN(targetObj)?.toString();
1701
- cell.accessoryHtml = `<dx-ref-tag refId=${dxn} class="dx-button is-6 pli-[3px] pbe-[2px] min-bs-0 absolute inline-end-1"><dx-icon icon="ph--link-simple--regular"/></dx-ref-tag>`;
1702
- }
1703
- }
1704
- if (props.format === FormatEnum4.Boolean) {
1705
- const value = getValue3(obj, field.path);
1706
- cell.accessoryHtml = tableControls.switch.render({
1707
- colIndex,
1708
- rowIndex: displayIndex,
1709
- checked: value ?? false
1710
- });
1711
- cell.readonly = "no-text-select";
1712
- }
1713
- if (props.format === FormatEnum4.SingleSelect) {
1714
- const value = getValue3(obj, field.path);
1715
- const options = this.model.projection.getFieldProjection(field.id).props.options;
1716
- if (options) {
1717
- const option = options.find((o) => o.id === value);
1718
- if (option) {
1719
- cell.accessoryHtml = `<span class="dx-tag" data-hue="${option.color}">${option.title}</span>`;
1720
- }
1721
- }
1722
- }
1723
- if (props.format === FormatEnum4.MultiSelect) {
1724
- const values = getValue3(obj, field.path);
1725
- const options = this.model.projection.getFieldProjection(field.id).props.options;
1726
- if (options && values && values.length > 0) {
1727
- const tags = values.map((value) => {
1728
- const option = options.find((o) => o.id === value);
1729
- if (option) {
1730
- return `<span class="dx-tag" data-hue="${option.color}">${option.title}</span>`;
1731
- }
1732
- return null;
1733
- }).filter(Boolean).join("");
1734
- if (tags) {
1735
- cell.accessoryHtml = tags;
1736
- }
1737
- }
1738
- }
1739
- const idx = toPlaneCellIndex({
1740
- col: colIndex,
1741
- row: displayIndex
1742
- });
1743
- cells[idx] = cell;
1744
- }
1745
- getMainGridCells(range) {
1746
- const cells = {};
1747
- const fields = this.model.view?.fields ?? [];
1748
- for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
1749
- for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1750
- const field = fields[col];
1751
- if (!field) {
1752
- continue;
1753
- }
1754
- this.createDataCell(cells, this.model.rows.value[row], field, col, row);
1755
- }
1756
- }
1757
- return cells;
1758
- }
1759
- getDraftRowCells(range) {
1760
- const cells = {};
1761
- const fields = this.model.view?.fields ?? [];
1762
- const draftRows = this.model.draftRows.value;
1763
- for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
1764
- const draftRow = draftRows[row];
1765
- for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1766
- const field = fields[col];
1767
- if (!field) {
1768
- continue;
1769
- }
1770
- this.createDataCell(cells, draftRow.data, field, col, row);
1771
- if (this.model.hasDraftRowValidationError(row, field.path)) {
1772
- const cellIndex = toPlaneCellIndex({
1773
- col,
1774
- row
1775
- });
1776
- const cellValue = cells[cellIndex];
1777
- if (cellValue) {
1778
- const existingClasses = cellValue.className || "";
1779
- const draftClasses = "dx-grid__cell--flagged";
1780
- cellValue.className = existingClasses ? `${existingClasses} ${draftClasses}` : draftClasses;
1781
- }
1782
- }
1783
- }
1784
- }
1785
- return cells;
1786
- }
1787
- getHeaderCells(range) {
1788
- const cells = {};
1789
- const fields = this.model.view?.fields ?? [];
1790
- for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1791
- const { field, props } = this.getFieldProjection(fields[col].id);
1792
- const sorting = this.model.sorting?.sorting;
1793
- const direction = sorting?.fieldId === field.id ? sorting.direction : void 0;
1794
- cells[toPlaneCellIndex({
1795
- col,
1796
- row: 0
1797
- })] = {
1798
- // TODO(burdon): Use same logic as form for fallback title.
1799
- value: props.title ?? field.path,
1800
- readonly: true,
1801
- resizeHandle: "col",
1802
- accessoryHtml: `
1803
- ${direction !== void 0 ? tableButtons.sort.render({
1804
- fieldId: field.id,
1805
- direction
1806
- }) : ""}
1807
- ${tableButtons.columnSettings.render({
1808
- fieldId: field.id
1809
- })}
1810
- `,
1811
- className: "!bg-toolbarSurface"
1812
- };
1813
- }
1814
- return cells;
1815
- }
1816
- getSelectionColumnCells(range) {
1817
- const cells = {};
1818
- for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
1819
- const isSelected = this.model.selection.isRowIndexSelected(row);
1820
- const classes = cellClassesForRowSelection(isSelected, this.model.selection.selectionMode);
1821
- cells[toPlaneCellIndex({
1822
- col: 0,
1823
- row
1824
- })] = {
1825
- value: "",
1826
- readonly: "no-text-select",
1827
- className: classes ? mx(classes) : void 0,
1828
- accessoryHtml: tableControls.checkbox.render({
1829
- rowIndex: row,
1830
- checked: isSelected
1831
- })
1832
- };
1833
- }
1834
- return cells;
1835
- }
1836
- getActionColumnCells(range) {
1837
- const cells = {};
1838
- for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
1839
- const isSelected = this.model.selection.isRowIndexSelected(row);
1840
- const classes = cellClassesForRowSelection(isSelected, this.model.selection.selectionMode);
1841
- cells[toPlaneCellIndex({
1842
- col: 0,
1843
- row
1844
- })] = {
1845
- value: "",
1846
- readonly: true,
1847
- className: classes ? mx(classes) : void 0,
1848
- accessoryHtml: tableButtons.rowMenu.render({
1849
- rowIndex: row
1850
- })
1851
- };
1852
- }
1853
- return cells;
1854
- }
1855
- getSelectAllCell() {
1856
- if (!this.model.features.selection.enabled || this.model.selection.selectionMode === "single") {
1857
- return {
1858
- [toPlaneCellIndex({
1859
- col: 0,
1860
- row: 0
1861
- })]: {
1862
- className: "!bg-toolbarSurface",
1863
- readonly: true,
1864
- value: ""
1865
- }
1866
- };
1867
- }
1868
- return {
1869
- [toPlaneCellIndex({
1870
- col: 0,
1871
- row: 0
1872
- })]: {
1873
- accessoryHtml: tableControls.checkbox.render({
1874
- rowIndex: 0,
1875
- header: true,
1876
- checked: this.model.selection.allRowsSeleted.value
1877
- }),
1878
- className: "!bg-toolbarSurface",
1879
- readonly: true,
1880
- value: ""
1881
- }
1882
- };
1883
- }
1884
- getNewColumnCell() {
1885
- return {
1886
- [toPlaneCellIndex({
1887
- col: 0,
1888
- row: 0
1889
- })]: {
1890
- accessoryHtml: this.model.features.schemaEditable ? tableButtons.addColumn.render({
1891
- disabled: (this.model.view?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT
1892
- }) : void 0,
1893
- className: "!bg-toolbarSurface",
1894
- readonly: true,
1895
- value: ""
1896
- }
1897
- };
1898
- }
1899
- getDraftActionCells(range) {
1900
- const cells = {};
1901
- const draftRows = this.model.draftRows.value;
1902
- for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
1903
- const draftRow = draftRows[row];
1904
- const disabled = !draftRow.valid;
1905
- cells[toPlaneCellIndex({
1906
- col: 0,
1907
- row
1908
- })] = {
1909
- value: "",
1910
- readonly: true,
1911
- accessoryHtml: tableButtons.saveDraftRow.render({
1912
- rowIndex: row,
1913
- disabled
1914
- })
1915
- };
1916
- }
1917
- return cells;
1918
- }
1919
- };
1920
- var cellClassesForRowSelection = (selected, selectionMode) => {
1921
- if (!selected) {
1922
- if (selectionMode === "single") {
1923
- return [
1924
- "!cursor-pointer"
1925
- ];
1926
- } else {
1927
- return void 0;
1928
- }
1929
- }
1930
- switch (selectionMode) {
1931
- case "single":
1932
- return [
1933
- "!bg-currentRelated hover:bg-hoverSurface",
1934
- "!cursor-pointer"
1935
- ];
1936
- case "multiple":
1937
- return [
1938
- "!bg-gridCellSelected"
1939
- ];
1940
- }
1941
- };
66
+ import { isNotFalsy, safeParseInt } from "@dxos/util";
1942
67
 
1943
68
  // src/components/TableCellEditor/TableCellEditor.tsx
1944
- import { useSignals as _useSignals7 } from "@preact-signals/safe-react/tracking";
1945
- import React7, { useCallback as useCallback4, useEffect as useEffect3, useMemo as useMemo4, useRef as useRef2, useState as useState2 } from "react";
1946
- import { FormatEnum as FormatEnum5, TypeEnum as TypeEnum4 } from "@dxos/echo-schema";
1947
- import { invariant as invariant3 } from "@dxos/invariant";
69
+ import { useSignals as _useSignals3 } from "@preact-signals/safe-react/tracking";
70
+ import React3, { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
71
+ import { FormatEnum, TypeEnum } from "@dxos/echo-schema";
72
+ import { invariant as invariant2 } from "@dxos/invariant";
1948
73
  import { useThemeContext } from "@dxos/react-ui";
1949
74
  import { createMarkdownExtensions } from "@dxos/react-ui-editor";
1950
- import { cellQuery, editorKeys, parseCellIndex as parseCellIndex2, useGridContext as useGridContext4, GridCellEditor } from "@dxos/react-ui-grid";
1951
- import { tagPicker, createLinks } from "@dxos/react-ui-tag-picker";
75
+ import { GridCellEditor, cellQuery, editorKeys, parseCellIndex as parseCellIndex2, useGridContext as useGridContext3 } from "@dxos/react-ui-grid";
76
+ import { createLinks, tagPicker } from "@dxos/react-ui-tag-picker";
1952
77
 
1953
78
  // src/components/TableCellEditor/CellValidationMessage.tsx
1954
- import { useSignals as _useSignals5 } from "@preact-signals/safe-react/tracking";
1955
- import React5 from "react";
1956
- import { useGridContext as useGridContext2 } from "@dxos/react-ui-grid";
79
+ import { useSignals as _useSignals } from "@preact-signals/safe-react/tracking";
80
+ import React from "react";
81
+ import { useGridContext } from "@dxos/react-ui-grid";
1957
82
  var CellValidationMessage = ({ validationError, variant = "error", __gridScope }) => {
1958
- var _effect = _useSignals5();
83
+ var _effect = _useSignals();
1959
84
  try {
1960
- const { editing, editBox: box } = useGridContext2("GridSheetCellEditor", __gridScope);
85
+ const { editing, editBox: box } = useGridContext("GridSheetCellEditor", __gridScope);
1961
86
  if (!editing || !validationError) {
1962
87
  return null;
1963
88
  }
1964
89
  const bgClass = variant === "error" ? "bg-errorSurface" : "bg-warningSurface";
1965
90
  const textClass = variant === "error" ? "text-errorSurfaceText" : "text-warningSurfaceText";
1966
- return /* @__PURE__ */ React5.createElement("div", {
91
+ return /* @__PURE__ */ React.createElement("div", {
1967
92
  role: "none",
1968
93
  className: `absolute ${bgClass} ${textClass} rounded-bs-sm text-xs p-1`,
1969
94
  style: {
@@ -1981,107 +106,6 @@ var CellValidationMessage = ({ validationError, variant = "error", __gridScope }
1981
106
  }
1982
107
  };
1983
108
 
1984
- // src/components/TableCellEditor/FormCellEditor.tsx
1985
- import { useSignals as _useSignals6 } from "@preact-signals/safe-react/tracking";
1986
- import React6, { useCallback as useCallback3, useEffect as useEffect2, useMemo as useMemo3, useRef } from "react";
1987
- import { getSnapshot as getSnapshot2 } from "@dxos/echo-schema";
1988
- import { invariant as invariant2 } from "@dxos/invariant";
1989
- import { Popover as Popover3 } from "@dxos/react-ui";
1990
- import { Form as Form2 } from "@dxos/react-ui-form";
1991
- import { parseCellIndex, useGridContext as useGridContext3 } from "@dxos/react-ui-grid";
1992
- import { getDeep, setDeep } from "@dxos/util";
1993
- var __dxlog_file3 = "/__w/dxos/dxos/packages/ui/react-ui-table/src/components/TableCellEditor/FormCellEditor.tsx";
1994
- var FormCellEditor = ({ fieldProjection, model, schema, __gridScope }) => {
1995
- var _effect = _useSignals6();
1996
- try {
1997
- const { editing, setEditing } = useGridContext3("ArrayEditor", __gridScope);
1998
- const cellRef = useRef(null);
1999
- useEffect2(() => {
2000
- if (editing && editing.cellElement) {
2001
- cellRef.current = editing.cellElement;
2002
- }
2003
- }, [
2004
- editing?.cellElement
2005
- ]);
2006
- const narrowedSchema = useMemo3(() => {
2007
- if (!schema) {
2008
- return void 0;
2009
- }
2010
- return narrowSchema(schema, [
2011
- fieldProjection.field.path
2012
- ]);
2013
- }, [
2014
- JSON.stringify(schema),
2015
- fieldProjection.field.path
2016
- ]);
2017
- const originalRow = useMemo3(() => {
2018
- if (model && editing) {
2019
- const cell = parseCellIndex(editing.index);
2020
- const row = model.getRowAt(cell.row);
2021
- invariant2(row, void 0, {
2022
- F: __dxlog_file3,
2023
- L: 47,
2024
- S: void 0,
2025
- A: [
2026
- "row",
2027
- ""
2028
- ]
2029
- });
2030
- return row;
2031
- }
2032
- return void 0;
2033
- }, [
2034
- model,
2035
- editing
2036
- ]);
2037
- const formValues = useMemo3(() => {
2038
- if (originalRow) {
2039
- return getSnapshot2(originalRow);
2040
- }
2041
- }, [
2042
- originalRow
2043
- ]);
2044
- const handleSave = useCallback3((values) => {
2045
- const path = fieldProjection.field.path;
2046
- const value = getDeep(values, [
2047
- path
2048
- ]);
2049
- setDeep(originalRow, [
2050
- path
2051
- ], value);
2052
- setEditing(null);
2053
- }, [
2054
- fieldProjection.field.path,
2055
- originalRow
2056
- ]);
2057
- const handleOpenChange = useCallback3((nextOpen) => {
2058
- if (nextOpen === false) {
2059
- setEditing(null);
2060
- }
2061
- }, [
2062
- setEditing
2063
- ]);
2064
- if (!editing) {
2065
- return null;
2066
- }
2067
- return /* @__PURE__ */ React6.createElement(Popover3.Root, {
2068
- open: editing !== null,
2069
- onOpenChange: handleOpenChange
2070
- }, /* @__PURE__ */ React6.createElement(Popover3.VirtualTrigger, {
2071
- virtualRef: cellRef
2072
- }), /* @__PURE__ */ React6.createElement(Popover3.Content, {
2073
- tabIndex: -1,
2074
- classNames: "popover-card-width"
2075
- }, /* @__PURE__ */ React6.createElement(Popover3.Arrow, null), /* @__PURE__ */ React6.createElement(Popover3.Viewport, null, /* @__PURE__ */ React6.createElement(Form2, {
2076
- values: formValues,
2077
- schema: narrowedSchema,
2078
- onSave: handleSave
2079
- }))));
2080
- } finally {
2081
- _effect.f();
2082
- }
2083
- };
2084
-
2085
109
  // src/components/TableCellEditor/extension.ts
2086
110
  import { autocompletion, pickedCompletion } from "@codemirror/autocomplete";
2087
111
  import { EditorView } from "@codemirror/view";
@@ -2121,28 +145,136 @@ var completion = ({ debug, onQuery, onMatch }) => {
2121
145
  ];
2122
146
  };
2123
147
 
148
+ // src/components/TableCellEditor/FormCellEditor.tsx
149
+ import { useSignals as _useSignals2 } from "@preact-signals/safe-react/tracking";
150
+ import React2, { useCallback, useEffect, useMemo, useRef, useState } from "react";
151
+ import { getSnapshot } from "@dxos/echo-schema";
152
+ import { invariant } from "@dxos/invariant";
153
+ import { Popover } from "@dxos/react-ui";
154
+ import { Form } from "@dxos/react-ui-form";
155
+ import { parseCellIndex, useGridContext as useGridContext2 } from "@dxos/react-ui-grid";
156
+ import { getDeep, setDeep } from "@dxos/util";
157
+ var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-table/src/components/TableCellEditor/FormCellEditor.tsx";
158
+ var FormCellEditor = ({ fieldProjection, model, schema, onSave, __gridScope }) => {
159
+ var _effect = _useSignals2();
160
+ try {
161
+ const { editing: contextEditing, setEditing } = useGridContext2("ArrayEditor", __gridScope);
162
+ const [editing, setLocalEditing] = useState(false);
163
+ const anchorRef = useRef(null);
164
+ useEffect(() => {
165
+ if (contextEditing && contextEditing.cellElement) {
166
+ anchorRef.current = contextEditing.cellElement.querySelector(".dx-grid__cell__content");
167
+ setLocalEditing(true);
168
+ } else {
169
+ anchorRef.current = null;
170
+ setLocalEditing(false);
171
+ }
172
+ }, [
173
+ contextEditing
174
+ ]);
175
+ const narrowedSchema = useMemo(() => {
176
+ if (!schema) {
177
+ return void 0;
178
+ }
179
+ return narrowSchema(schema, [
180
+ fieldProjection.field.path
181
+ ]);
182
+ }, [
183
+ JSON.stringify(schema),
184
+ fieldProjection.field.path
185
+ ]);
186
+ const originalRow = useMemo(() => {
187
+ if (model && contextEditing) {
188
+ const cell = parseCellIndex(contextEditing.index);
189
+ const row = model.getRowAt(cell.row);
190
+ invariant(row, void 0, {
191
+ F: __dxlog_file,
192
+ L: 55,
193
+ S: void 0,
194
+ A: [
195
+ "row",
196
+ ""
197
+ ]
198
+ });
199
+ return row;
200
+ }
201
+ return void 0;
202
+ }, [
203
+ model,
204
+ contextEditing
205
+ ]);
206
+ const formValues = useMemo(() => {
207
+ if (originalRow) {
208
+ return getSnapshot(originalRow);
209
+ }
210
+ }, [
211
+ originalRow
212
+ ]);
213
+ const handleSave = useCallback((values) => {
214
+ const path = fieldProjection.field.path;
215
+ const value = getDeep(values, [
216
+ path
217
+ ]);
218
+ setDeep(originalRow, [
219
+ path
220
+ ], value);
221
+ setEditing(null);
222
+ setLocalEditing(false);
223
+ onSave?.();
224
+ }, [
225
+ fieldProjection.field.path,
226
+ originalRow
227
+ ]);
228
+ const handleOpenChange = useCallback((nextOpen) => {
229
+ if (nextOpen === false) {
230
+ setEditing(null);
231
+ onSave?.();
232
+ }
233
+ setLocalEditing(nextOpen);
234
+ }, []);
235
+ if (!editing) {
236
+ return null;
237
+ }
238
+ return /* @__PURE__ */ React2.createElement(Popover.Root, {
239
+ open: editing,
240
+ onOpenChange: handleOpenChange
241
+ }, /* @__PURE__ */ React2.createElement(Popover.VirtualTrigger, {
242
+ virtualRef: anchorRef
243
+ }), /* @__PURE__ */ React2.createElement(Popover.Content, {
244
+ tabIndex: -1,
245
+ classNames: "popover-card-width density-fine"
246
+ }, /* @__PURE__ */ React2.createElement(Popover.Arrow, null), /* @__PURE__ */ React2.createElement(Popover.Viewport, null, /* @__PURE__ */ React2.createElement(Form, {
247
+ values: formValues,
248
+ schema: narrowedSchema,
249
+ onSave: handleSave
250
+ }))));
251
+ } finally {
252
+ _effect.f();
253
+ }
254
+ };
255
+
2124
256
  // src/components/TableCellEditor/TableCellEditor.tsx
2125
- var __dxlog_file4 = "/__w/dxos/dxos/packages/ui/react-ui-table/src/components/TableCellEditor/TableCellEditor.tsx";
257
+ var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/react-ui-table/src/components/TableCellEditor/TableCellEditor.tsx";
2126
258
  var newValue = Symbol.for("newValue");
2127
259
  var createOption = (text) => ({
2128
260
  [newValue]: true,
2129
261
  text
2130
262
  });
2131
263
  var isCreateOption = (data) => typeof data === "object" && data[newValue];
2132
- var TableValueEditor = ({ model, modals, schema, onEnter, onFocus, onQuery, __gridScope }) => {
2133
- var _effect = _useSignals7();
264
+ var TableValueEditor = ({ model, modals, schema, onEnter, onFocus, onSave, onQuery, __gridScope }) => {
265
+ var _effect = _useSignals3();
2134
266
  try {
2135
- const { editing } = useGridContext4("TableValueEditor", __gridScope);
2136
- const fieldProjection = useMemo4(() => {
267
+ const { editing } = useGridContext3("TableValueEditor", __gridScope);
268
+ const fieldProjection = useMemo2(() => {
2137
269
  if (!model || !editing) {
2138
270
  return;
2139
271
  }
2140
272
  const { col } = parseCellIndex2(editing.index);
2141
- const field = model.projection.view.fields[col];
273
+ const field = model.projection.fields[col];
2142
274
  const fieldProjection2 = model.projection.getFieldProjection(field.id);
2143
- invariant3(fieldProjection2, void 0, {
2144
- F: __dxlog_file4,
2145
- L: 74,
275
+ invariant2(fieldProjection2, void 0, {
276
+ F: __dxlog_file2,
277
+ L: 76,
2146
278
  S: void 0,
2147
279
  A: [
2148
280
  "fieldProjection",
@@ -2154,20 +286,22 @@ var TableValueEditor = ({ model, modals, schema, onEnter, onFocus, onQuery, __gr
2154
286
  model,
2155
287
  editing
2156
288
  ]);
2157
- if (fieldProjection?.props.type === TypeEnum4.Array) {
2158
- return /* @__PURE__ */ React7.createElement(FormCellEditor, {
289
+ if (fieldProjection?.props.type === TypeEnum.Array || fieldProjection?.props.format === FormatEnum.SingleSelect) {
290
+ return /* @__PURE__ */ React3.createElement(FormCellEditor, {
2159
291
  fieldProjection,
2160
292
  model,
2161
293
  schema,
2162
- __gridScope
294
+ __gridScope,
295
+ onSave
2163
296
  });
2164
297
  }
2165
- return /* @__PURE__ */ React7.createElement(TableCellEditor, {
298
+ return /* @__PURE__ */ React3.createElement(TableCellEditor, {
2166
299
  model,
2167
300
  modals,
2168
301
  onEnter,
2169
302
  onFocus,
2170
303
  onQuery,
304
+ onSave,
2171
305
  __gridScope
2172
306
  });
2173
307
  } finally {
@@ -2179,24 +313,24 @@ var editorSlots = {
2179
313
  className: "!plb-[--dx-grid-cell-editor-padding-block]"
2180
314
  }
2181
315
  };
2182
- var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }) => {
2183
- var _effect = _useSignals7();
316
+ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, onSave, __gridScope }) => {
317
+ var _effect = _useSignals3();
2184
318
  try {
2185
- const { id: gridId, editing, setEditing } = useGridContext4("TableCellEditor", __gridScope);
319
+ const { id: gridId, editing, setEditing } = useGridContext3("TableCellEditor", __gridScope);
2186
320
  const suppressNextBlur = useRef2(false);
2187
321
  const { themeMode } = useThemeContext();
2188
- const [_validationError, setValidationError] = useState2(null);
2189
- const [_validationVariant, setValidationVariant] = useState2("error");
2190
- const fieldProjection = useMemo4(() => {
322
+ const [validationError, setValidationError] = useState2(null);
323
+ const [validationVariant, setValidationVariant] = useState2("error");
324
+ const fieldProjection = useMemo2(() => {
2191
325
  if (!model || !editing) {
2192
326
  return;
2193
327
  }
2194
328
  const { col } = parseCellIndex2(editing.index);
2195
- const field = model.projection.view.fields[col];
329
+ const field = model.projection.fields[col];
2196
330
  const fieldProjection2 = model.projection.getFieldProjection(field.id);
2197
- invariant3(fieldProjection2, void 0, {
2198
- F: __dxlog_file4,
2199
- L: 119,
331
+ invariant2(fieldProjection2, void 0, {
332
+ F: __dxlog_file2,
333
+ L: 135,
2200
334
  S: void 0,
2201
335
  A: [
2202
336
  "fieldProjection",
@@ -2208,7 +342,7 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
2208
342
  model,
2209
343
  editing
2210
344
  ]);
2211
- useEffect3(() => {
345
+ useEffect2(() => {
2212
346
  if (!model || !editing || !fieldProjection) {
2213
347
  setValidationError(null);
2214
348
  return;
@@ -2216,15 +350,15 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
2216
350
  const cell = parseCellIndex2(editing.index);
2217
351
  const { row, col } = cell;
2218
352
  if (model.isDraftCell(cell)) {
2219
- const field = model.projection.view.fields[col];
353
+ const field = model.projection.fields[col];
2220
354
  const hasValidationError = model.hasDraftRowValidationError(row, field.path);
2221
355
  if (hasValidationError) {
2222
356
  const draftRows = model.draftRows.value;
2223
357
  if (row >= 0 && row < draftRows.length) {
2224
358
  const draftRow = draftRows[row];
2225
- const validationError = draftRow.validationErrors?.find((error) => error.path === field.path);
2226
- if (validationError) {
2227
- setValidationError(validationError.message);
359
+ const validationError2 = draftRow.validationErrors?.find((error) => error.path === field.path);
360
+ if (validationError2) {
361
+ setValidationError(validationError2.message);
2228
362
  setValidationVariant("warning");
2229
363
  }
2230
364
  }
@@ -2239,7 +373,7 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
2239
373
  editing,
2240
374
  fieldProjection
2241
375
  ]);
2242
- const handleEnter = useCallback4(async (value) => {
376
+ const handleEnter = useCallback2(async (value) => {
2243
377
  if (!model || !editing) {
2244
378
  return;
2245
379
  }
@@ -2251,6 +385,7 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
2251
385
  onEnter?.(cell);
2252
386
  onFocus?.();
2253
387
  setEditing(null);
388
+ onSave?.();
2254
389
  } else {
2255
390
  setValidationError(validationResult.error);
2256
391
  setValidationVariant("error");
@@ -2262,7 +397,7 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
2262
397
  onFocus,
2263
398
  setEditing
2264
399
  ]);
2265
- const handleBlur = useCallback4(async (value) => {
400
+ const handleBlur = useCallback2(async (value) => {
2266
401
  if (!model || !editing) {
2267
402
  return;
2268
403
  }
@@ -2273,7 +408,7 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
2273
408
  model,
2274
409
  editing
2275
410
  ]);
2276
- const handleClose = useCallback4(async (value, event) => {
411
+ const handleClose = useCallback2(async (value, event) => {
2277
412
  if (!model || !editing || !fieldProjection) {
2278
413
  return;
2279
414
  }
@@ -2284,9 +419,10 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
2284
419
  setValidationError(null);
2285
420
  model.setCellData(cell, value);
2286
421
  setEditing(null);
422
+ onSave?.();
2287
423
  onEnter?.(cell);
2288
424
  if (event && onFocus) {
2289
- onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
425
+ onFocus(determineNavigationAxis(event), determineNavigationDelta(event), cell);
2290
426
  }
2291
427
  } else {
2292
428
  setValidationError(result.error);
@@ -2295,6 +431,7 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
2295
431
  } else {
2296
432
  setValidationError(null);
2297
433
  setEditing(null);
434
+ onSave?.();
2298
435
  if (event && onFocus) {
2299
436
  onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
2300
437
  }
@@ -2305,9 +442,10 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
2305
442
  onFocus,
2306
443
  onEnter,
2307
444
  fieldProjection,
2308
- setEditing
445
+ setEditing,
446
+ onSave
2309
447
  ]);
2310
- const extension = useMemo4(() => {
448
+ const extension = useMemo2(() => {
2311
449
  if (!fieldProjection) {
2312
450
  return [];
2313
451
  }
@@ -2320,12 +458,12 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
2320
458
  })
2321
459
  ];
2322
460
  const format = fieldProjection.props.format;
2323
- if (format === FormatEnum5.SingleSelect || format === FormatEnum5.MultiSelect) {
461
+ if (format === FormatEnum.SingleSelect || format === FormatEnum.MultiSelect) {
2324
462
  extension2.push(createMarkdownExtensions({
2325
463
  themeMode
2326
464
  }));
2327
465
  const options = fieldProjection.props.options || [];
2328
- const mode = format === FormatEnum5.SingleSelect ? "single-select" : "multi-select";
466
+ const mode = format === FormatEnum.SingleSelect ? "single-select" : "multi-select";
2329
467
  extension2.push(tagPicker({
2330
468
  mode,
2331
469
  keymap: false,
@@ -2350,137 +488,373 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
2350
488
  }
2351
489
  }));
2352
490
  }
2353
- if (onQuery) {
2354
- switch (fieldProjection.props.format) {
2355
- case FormatEnum5.Ref: {
2356
- extension2.push([
2357
- completion({
2358
- onQuery: (text) => onQuery(fieldProjection, text),
2359
- onMatch: (data) => {
2360
- if (model && editing && modals) {
2361
- if (isCreateOption(data)) {
2362
- const { field, props } = fieldProjection;
2363
- if (props.referenceSchema) {
2364
- suppressNextBlur.current = true;
2365
- modals.openCreateRef(props.referenceSchema, document.querySelector(cellQuery(editing.index, gridId)), {
2366
- [field.referencePath]: data.text
2367
- }, (data2) => {
2368
- void handleEnter(data2);
2369
- });
2370
- }
2371
- } else {
2372
- void handleEnter(data);
2373
- }
2374
- }
2375
- }
2376
- })
2377
- ]);
2378
- break;
2379
- }
2380
- }
491
+ if (onQuery) {
492
+ switch (fieldProjection.props.format) {
493
+ case FormatEnum.Ref: {
494
+ extension2.push([
495
+ completion({
496
+ onQuery: (text) => onQuery(fieldProjection, text),
497
+ onMatch: (data) => {
498
+ if (model && editing && modals) {
499
+ if (isCreateOption(data)) {
500
+ const { field, props } = fieldProjection;
501
+ if (props.referenceSchema) {
502
+ suppressNextBlur.current = true;
503
+ modals.openCreateRef(props.referenceSchema, document.querySelector(cellQuery(editing.index, gridId)), {
504
+ [field.referencePath]: data.text
505
+ }, (data2) => {
506
+ void handleEnter(data2);
507
+ });
508
+ }
509
+ } else {
510
+ void handleEnter(data);
511
+ }
512
+ }
513
+ }
514
+ })
515
+ ]);
516
+ break;
517
+ }
518
+ }
519
+ }
520
+ return extension2;
521
+ }, [
522
+ model,
523
+ modals,
524
+ editing,
525
+ fieldProjection,
526
+ handleClose,
527
+ themeMode
528
+ ]);
529
+ const getCellContent = useCallback2(() => {
530
+ if (model && editing) {
531
+ const cell = parseCellIndex2(editing.index);
532
+ const { col } = cell;
533
+ const field = model.projection.fields[col];
534
+ const fieldProjection2 = model.projection.getFieldProjection(field.id);
535
+ if (fieldProjection2?.props.format === FormatEnum.SingleSelect || fieldProjection2?.props.format === FormatEnum.MultiSelect) {
536
+ const value2 = model.getCellData(cell);
537
+ if (value2 !== void 0) {
538
+ const options = fieldProjection2.props.options || [];
539
+ if (fieldProjection2.props.format === FormatEnum.MultiSelect) {
540
+ const tagItems = value2.split(",").map((id) => {
541
+ const option = options.find((o) => o.id === id);
542
+ if (option) {
543
+ return {
544
+ id,
545
+ label: option.title,
546
+ hue: option.color
547
+ };
548
+ }
549
+ return void 0;
550
+ }).filter((item) => item !== void 0);
551
+ return createLinks(tagItems);
552
+ } else {
553
+ const option = options.find((o) => o.id === value2);
554
+ if (option) {
555
+ const tagItem = {
556
+ id: value2,
557
+ label: option.title,
558
+ hue: option.color
559
+ };
560
+ return createLinks([
561
+ tagItem
562
+ ]);
563
+ }
564
+ }
565
+ }
566
+ return "";
567
+ }
568
+ const value = model.getCellData(cell);
569
+ return value !== void 0 ? String(value) : "";
570
+ }
571
+ }, [
572
+ model,
573
+ editing
574
+ ]);
575
+ return /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(CellValidationMessage, {
576
+ validationError,
577
+ variant: validationVariant,
578
+ __gridScope
579
+ }), /* @__PURE__ */ React3.createElement(GridCellEditor, {
580
+ extension,
581
+ getCellContent,
582
+ onBlur: handleBlur,
583
+ slots: editorSlots
584
+ }));
585
+ } finally {
586
+ _effect.f();
587
+ }
588
+ };
589
+ var determineNavigationAxis = ({ key }) => {
590
+ switch (key) {
591
+ case "ArrowUp":
592
+ case "ArrowDown":
593
+ case "Enter": {
594
+ return "row";
595
+ }
596
+ case "ArrowLeft":
597
+ case "ArrowRight":
598
+ case "Tab":
599
+ return "col";
600
+ }
601
+ };
602
+ var determineNavigationDelta = ({ key, shift }) => {
603
+ switch (key) {
604
+ case "ArrowUp":
605
+ case "ArrowLeft":
606
+ return -1;
607
+ case "ArrowDown":
608
+ case "ArrowRight":
609
+ return 1;
610
+ }
611
+ return shift ? -1 : 1;
612
+ };
613
+
614
+ // src/components/Table/ColumnActionsMenu.tsx
615
+ import { useSignals as _useSignals4 } from "@preact-signals/safe-react/tracking";
616
+ import React4 from "react";
617
+ import { DropdownMenu, useTranslation } from "@dxos/react-ui";
618
+ var ColumnActionsMenu = ({ model, modals }) => {
619
+ var _effect = _useSignals4();
620
+ try {
621
+ const { t } = useTranslation(translationKey);
622
+ const state = modals.state.value;
623
+ if (state?.type !== "column") {
624
+ return null;
625
+ }
626
+ const currentSort = model.sorting?.sorting;
627
+ const isCurrentColumnSorted = currentSort?.fieldId === state.fieldId;
628
+ return /* @__PURE__ */ React4.createElement(DropdownMenu.Root, {
629
+ modal: false,
630
+ open: true,
631
+ onOpenChange: modals.close
632
+ }, /* @__PURE__ */ React4.createElement(DropdownMenu.VirtualTrigger, {
633
+ virtualRef: modals.trigger
634
+ }), /* @__PURE__ */ React4.createElement(DropdownMenu.Portal, null, /* @__PURE__ */ React4.createElement(DropdownMenu.Content, null, /* @__PURE__ */ React4.createElement(DropdownMenu.Viewport, null, (!isCurrentColumnSorted || currentSort?.direction === "asc") && /* @__PURE__ */ React4.createElement(DropdownMenu.Item, {
635
+ "data-testid": "column-sort-descending",
636
+ onClick: () => model.sorting?.setSort(state.fieldId, "desc")
637
+ }, t("column action sort descending")), (!isCurrentColumnSorted || currentSort?.direction === "desc") && /* @__PURE__ */ React4.createElement(DropdownMenu.Item, {
638
+ "data-testid": "column-sort-ascending",
639
+ onClick: () => model.sorting?.setSort(state.fieldId, "asc")
640
+ }, t("column action sort ascending")), isCurrentColumnSorted && /* @__PURE__ */ React4.createElement(DropdownMenu.Item, {
641
+ "data-testid": "column-clear-sort",
642
+ onClick: () => model.sorting?.clearSort()
643
+ }, t("column action clear sorting")), model.getColumnCount() > 1 && model.features.schemaEditable && /* @__PURE__ */ React4.createElement(DropdownMenu.Item, {
644
+ "data-testid": "column-delete",
645
+ onClick: () => model.deleteColumn(state.fieldId)
646
+ }, t("column action delete")), model.features.schemaEditable && /* @__PURE__ */ React4.createElement(DropdownMenu.Item, {
647
+ "data-testid": "column-settings",
648
+ onClick: () => modals.openColumnSettings()
649
+ }, t("column action settings"))), /* @__PURE__ */ React4.createElement(DropdownMenu.Arrow, null))));
650
+ } finally {
651
+ _effect.f();
652
+ }
653
+ };
654
+
655
+ // src/components/Table/ColumnSettings.tsx
656
+ import { useSignals as _useSignals5 } from "@preact-signals/safe-react/tracking";
657
+ import React5, { useCallback as useCallback3, useEffect as useEffect3, useMemo as useMemo3, useState as useState3 } from "react";
658
+ import { Popover as Popover2 } from "@dxos/react-ui";
659
+ import { FieldEditor } from "@dxos/react-ui-form";
660
+ var ColumnSettings = ({ registry, model, modals, onNewColumn }) => {
661
+ var _effect = _useSignals5();
662
+ try {
663
+ const [newField, setNewField] = useState3();
664
+ const state = modals.state.value;
665
+ useEffect3(() => {
666
+ if (state?.type === "columnSettings" && state.mode.type === "create" && model?.projection) {
667
+ setNewField(model.projection.createFieldProjection());
668
+ requestAnimationFrame(() => {
669
+ onNewColumn();
670
+ });
671
+ } else {
672
+ setNewField(void 0);
673
+ }
674
+ }, [
675
+ model?.projection,
676
+ state
677
+ ]);
678
+ const existingField = useMemo3(() => {
679
+ if (state?.type === "columnSettings") {
680
+ const { mode } = state;
681
+ if (mode.type === "edit") {
682
+ return model?.projection?.fields.find((f) => f.id === mode.fieldId);
683
+ }
684
+ }
685
+ return void 0;
686
+ }, [
687
+ model?.projection?.fields,
688
+ state
689
+ ]);
690
+ const field = existingField ?? newField;
691
+ const handleSave = useCallback3(() => {
692
+ modals.close();
693
+ }, [
694
+ modals
695
+ ]);
696
+ const handleCancel = useCallback3(() => {
697
+ if (state?.type === "columnSettings" && state.mode.type === "create" && newField) {
698
+ model?.projection.deleteFieldProjection(newField.id);
699
+ }
700
+ }, [
701
+ model?.projection,
702
+ state,
703
+ newField
704
+ ]);
705
+ if (!model?.projection || !field) {
706
+ return null;
707
+ }
708
+ return /* @__PURE__ */ React5.createElement(Popover2.Root, {
709
+ modal: false,
710
+ open: state?.type === "columnSettings"
711
+ }, /* @__PURE__ */ React5.createElement(Popover2.VirtualTrigger, {
712
+ virtualRef: modals.trigger
713
+ }), /* @__PURE__ */ React5.createElement(Popover2.Portal, null, /* @__PURE__ */ React5.createElement(Popover2.Content, {
714
+ classNames: "md:is-64"
715
+ }, /* @__PURE__ */ React5.createElement(Popover2.Viewport, null, /* @__PURE__ */ React5.createElement(FieldEditor, {
716
+ projection: model.projection,
717
+ field,
718
+ registry,
719
+ onSave: handleSave,
720
+ onCancel: handleCancel
721
+ })), /* @__PURE__ */ React5.createElement(Popover2.Arrow, null))));
722
+ } finally {
723
+ _effect.f();
724
+ }
725
+ };
726
+
727
+ // src/components/Table/CreateRefPanel.tsx
728
+ import { useSignals as _useSignals6 } from "@preact-signals/safe-react/tracking";
729
+ import { Schema } from "effect";
730
+ import React6, { useCallback as useCallback4, useMemo as useMemo4 } from "react";
731
+ import { live } from "@dxos/live-object";
732
+ import { getSpace } from "@dxos/react-client/echo";
733
+ import { Popover as Popover3 } from "@dxos/react-ui";
734
+ import { Form as Form2 } from "@dxos/react-ui-form";
735
+ import { useGridContext as useGridContext4 } from "@dxos/react-ui-grid";
736
+ var CreateRefPanel = ({ model, modals, __gridScope }) => {
737
+ var _effect = _useSignals6();
738
+ try {
739
+ const { id: gridId } = useGridContext4("TableCellEditor", __gridScope);
740
+ const space = model && getSpace(model?.view);
741
+ const state = modals.state.value;
742
+ const schema = useMemo4(() => {
743
+ if (!space || state?.type !== "createRefPanel") {
744
+ return;
745
+ }
746
+ const [schema2] = space.db.schemaRegistry.query({
747
+ typename: state.typename
748
+ }).runSync();
749
+ if (schema2) {
750
+ const omit = Schema.omit("id");
751
+ return omit(schema2);
2381
752
  }
2382
- return extension2;
2383
753
  }, [
2384
- model,
754
+ space,
755
+ state
756
+ ]);
757
+ const handleSave = useCallback4((values) => {
758
+ if (!model || !space || state?.type !== "createRefPanel") {
759
+ return;
760
+ }
761
+ const [schema2] = space.db.schemaRegistry.query({
762
+ typename: state.typename
763
+ }).runSync();
764
+ if (schema2) {
765
+ const obj = space.db.add(live(schema2, values));
766
+ state.onCreate?.(obj);
767
+ }
768
+ void modals.close();
769
+ }, [
2385
770
  modals,
2386
- editing,
2387
- fieldProjection,
2388
- handleClose,
2389
- themeMode
771
+ space,
772
+ state
2390
773
  ]);
2391
- const getCellContent = useCallback4(() => {
2392
- if (model && editing) {
2393
- const cell = parseCellIndex2(editing.index);
2394
- const { col } = cell;
2395
- const field = model.projection.view.fields[col];
2396
- const fieldProjection2 = model.projection.getFieldProjection(field.id);
2397
- if (fieldProjection2?.props.format === FormatEnum5.SingleSelect || fieldProjection2?.props.format === FormatEnum5.MultiSelect) {
2398
- const value2 = model.getCellData(cell);
2399
- if (value2 !== void 0) {
2400
- const options = fieldProjection2.props.options || [];
2401
- if (fieldProjection2.props.format === FormatEnum5.MultiSelect) {
2402
- const tagItems = value2.split(",").map((id) => {
2403
- const option = options.find((o) => o.id === id);
2404
- if (option) {
2405
- return {
2406
- id,
2407
- label: option.title,
2408
- hue: option.color
2409
- };
2410
- }
2411
- return void 0;
2412
- }).filter((item) => item !== void 0);
2413
- return createLinks(tagItems);
2414
- } else {
2415
- const option = options.find((o) => o.id === value2);
2416
- if (option) {
2417
- const tagItem = {
2418
- id: value2,
2419
- label: option.title,
2420
- hue: option.color
2421
- };
2422
- return createLinks([
2423
- tagItem
2424
- ]);
2425
- }
2426
- }
2427
- }
2428
- return "";
2429
- }
2430
- const value = model.getCellData(cell);
2431
- return value !== void 0 ? String(value) : "";
774
+ const handleCancel = useCallback4(() => {
775
+ if (model) {
776
+ modals.close();
2432
777
  }
2433
778
  }, [
2434
- model,
2435
- editing
779
+ modals
2436
780
  ]);
2437
- return /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(CellValidationMessage, {
2438
- validationError: _validationError,
2439
- variant: _validationVariant,
2440
- __gridScope
2441
- }), /* @__PURE__ */ React7.createElement(GridCellEditor, {
2442
- extension,
2443
- getCellContent,
2444
- onBlur: handleBlur,
2445
- slots: editorSlots
2446
- }));
781
+ if (!model?.projection) {
782
+ return null;
783
+ }
784
+ return /* @__PURE__ */ React6.createElement(Popover3.Root, {
785
+ modal: false,
786
+ open: state?.type === "createRefPanel" && !!schema,
787
+ onOpenChange: (nextOpen) => {
788
+ if (model && !nextOpen) {
789
+ return modals.close();
790
+ }
791
+ }
792
+ }, /* @__PURE__ */ React6.createElement(Popover3.VirtualTrigger, {
793
+ virtualRef: modals.trigger
794
+ }), /* @__PURE__ */ React6.createElement(Popover3.Portal, null, /* @__PURE__ */ React6.createElement(Popover3.Content, {
795
+ classNames: "md:is-64",
796
+ "data-grid": gridId
797
+ }, /* @__PURE__ */ React6.createElement(Popover3.Viewport, null, state?.type === "createRefPanel" && schema && /* @__PURE__ */ React6.createElement(Form2, {
798
+ schema,
799
+ values: state.initialValues ?? {},
800
+ onSave: handleSave,
801
+ onCancel: handleCancel
802
+ })), /* @__PURE__ */ React6.createElement(Popover3.Arrow, null))));
2447
803
  } finally {
2448
804
  _effect.f();
2449
805
  }
2450
806
  };
2451
- var determineNavigationAxis = ({ key }) => {
2452
- switch (key) {
2453
- case "ArrowUp":
2454
- case "ArrowDown":
2455
- case "Enter": {
2456
- return "row";
807
+
808
+ // src/components/Table/RowActionsMenu.tsx
809
+ import { useSignals as _useSignals7 } from "@preact-signals/safe-react/tracking";
810
+ import React7 from "react";
811
+ import { DropdownMenu as DropdownMenu2, toLocalizedString, useTranslation as useTranslation2 } from "@dxos/react-ui";
812
+ var RowActionsMenu = ({ model, modals }) => {
813
+ var _effect = _useSignals7();
814
+ try {
815
+ const { t } = useTranslation2(translationKey);
816
+ const hasSelection = model.selection.hasSelection.value;
817
+ const state = modals.state.value;
818
+ if (state?.type !== "row") {
819
+ return null;
2457
820
  }
2458
- case "ArrowLeft":
2459
- case "ArrowRight":
2460
- case "Tab":
2461
- return "col";
2462
- }
2463
- };
2464
- var determineNavigationDelta = ({ key, shift }) => {
2465
- switch (key) {
2466
- case "ArrowUp":
2467
- case "ArrowLeft":
2468
- return -1;
2469
- case "ArrowDown":
2470
- case "ArrowRight":
2471
- return 1;
821
+ return /* @__PURE__ */ React7.createElement(DropdownMenu2.Root, {
822
+ modal: false,
823
+ open: true,
824
+ onOpenChange: modals.close
825
+ }, /* @__PURE__ */ React7.createElement(DropdownMenu2.VirtualTrigger, {
826
+ virtualRef: modals.trigger
827
+ }), /* @__PURE__ */ React7.createElement(DropdownMenu2.Content, null, /* @__PURE__ */ React7.createElement(DropdownMenu2.Viewport, null, model.features.dataEditable !== false && /* @__PURE__ */ React7.createElement(DropdownMenu2.Item, {
828
+ "data-testid": "row-menu-delete",
829
+ onClick: () => model.deleteRow(state.rowIndex)
830
+ }, t(hasSelection ? "bulk delete row label" : "delete row label")), model.rowActions?.map((action) => /* @__PURE__ */ React7.createElement(DropdownMenu2.Item, {
831
+ key: action.id,
832
+ "data-testid": `row-action-${action.id}`,
833
+ onClick: () => {
834
+ modals.close();
835
+ model.handleRowAction(action.id, state.rowIndex);
836
+ }
837
+ }, toLocalizedString(action.label, t)))), /* @__PURE__ */ React7.createElement(DropdownMenu2.Arrow, null)));
838
+ } finally {
839
+ _effect.f();
2472
840
  }
2473
- return shift ? -1 : 1;
2474
841
  };
2475
842
 
2476
843
  // src/components/Table/Table.tsx
2477
- var __dxlog_file5 = "/__w/dxos/dxos/packages/ui/react-ui-table/src/components/Table/Table.tsx";
2478
- var TableRoot = ({ children, role }) => {
844
+ var __dxlog_file3 = "/__w/dxos/dxos/packages/ui/react-ui-table/src/components/Table/Table.tsx";
845
+ var TableRoot = ({ children, role = "article" }) => {
2479
846
  var _effect = _useSignals8();
2480
847
  try {
2481
848
  return /* @__PURE__ */ React8.createElement("div", {
2482
849
  role: "none",
2483
- className: mx2("relative !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]")
850
+ className: mx("relative !border-separator [&_.dx-grid]:max-is-[--dx-grid-content-inline-size] [&_.dx-grid]:max-bs-[--dx-grid-content-block-size]", role === "card--popover" && "popover-card-height", role === "section" && "attention-surface", role === "card--intrinsic" && "[&_.dx-grid]:bs-[--dx-grid-content-block-size]", [
851
+ "card--popover",
852
+ "section",
853
+ "card--extrinsic"
854
+ ].includes(role) && "overflow-hidden", [
855
+ "article",
856
+ "slide"
857
+ ].includes(role) && "flex flex-col [&_.dx-grid]:grow [&_.dx-grid]:bs-0")
2484
858
  }, children);
2485
859
  } finally {
2486
860
  _effect.f();
@@ -2489,7 +863,7 @@ var TableRoot = ({ children, role }) => {
2489
863
  var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttention, schema, client, onRowClick }, forwardedRef) => {
2490
864
  var _effect = _useSignals8();
2491
865
  try {
2492
- const [dxGrid, setDxGrid] = useState3(null);
866
+ const [dxGrid, setDxGrid] = useState4(null);
2493
867
  const { hasAttention } = useAttention(model?.id ?? "table");
2494
868
  const { t } = useTranslation3(translationKey);
2495
869
  const modals = useMemo5(() => new ModalController(), []);
@@ -2526,19 +900,42 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
2526
900
  return {
2527
901
  update: (cell) => {
2528
902
  if (cell) {
2529
- dxGrid.updateIfWithinBounds(cell);
903
+ dxGrid.updateIfWithinBounds(cell, true);
2530
904
  } else {
2531
905
  dxGrid.updateCells(true);
2532
906
  dxGrid.requestUpdate();
2533
907
  }
908
+ },
909
+ focusDraft: () => {
910
+ requestAnimationFrame(() => {
911
+ dxGrid.setFocus({
912
+ plane: "frozenRowsEnd",
913
+ col: 0,
914
+ row: 0
915
+ });
916
+ dxGrid.refocus();
917
+ });
2534
918
  }
2535
919
  };
2536
920
  }, [
2537
921
  presentation,
2538
922
  dxGrid
2539
923
  ]);
924
+ const handleSaveDraftRow = useCallback5((rowIndex = 0) => {
925
+ if (model && dxGrid) {
926
+ const didCommitSuccessfully = model.commitDraftRow(rowIndex);
927
+ if (didCommitSuccessfully) {
928
+ requestAnimationFrame(() => {
929
+ dxGrid.scrollToEndRow();
930
+ });
931
+ }
932
+ }
933
+ }, [
934
+ model,
935
+ dxGrid
936
+ ]);
2540
937
  const handleGridClick = useCallback5((event) => {
2541
- const rowIndex = safeParseInt(event.target.ariaRowIndex ?? "");
938
+ const rowIndex = safeParseInt(event.target.closest("[aria-rowindex]")?.ariaRowIndex ?? "");
2542
939
  if (rowIndex != null) {
2543
940
  if (onRowClick) {
2544
941
  const row = model?.getRowAt(rowIndex);
@@ -2578,14 +975,7 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
2578
975
  break;
2579
976
  }
2580
977
  case "saveDraftRow": {
2581
- if (model) {
2582
- const didCommitSuccessfully = model.commitDraftRow(data.rowIndex);
2583
- if (dxGrid && didCommitSuccessfully) {
2584
- requestAnimationFrame(() => {
2585
- dxGrid.scrollToEndRow();
2586
- });
2587
- }
2588
- }
978
+ handleSaveDraftRow();
2589
979
  break;
2590
980
  }
2591
981
  }
@@ -2624,29 +1014,32 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
2624
1014
  modals,
2625
1015
  dxGrid
2626
1016
  ]);
2627
- const handleFocus = useCallback5((increment, delta) => {
2628
- if (dxGrid) {
2629
- dxGrid.refocus(increment, delta);
2630
- }
2631
- }, [
2632
- dxGrid
2633
- ]);
2634
- const handleEnter = useCallback5((cell) => {
2635
- if (!model?.features.dataEditable) {
2636
- return;
2637
- }
2638
- if (model && cell.row === model.getRowCount() - 1) {
2639
- model.insertRow();
2640
- if (dxGrid) {
2641
- requestAnimationFrame(() => {
2642
- dxGrid?.scrollToRow(cell.row + 1);
2643
- dxGrid?.refocus("row", 1);
1017
+ const handleFocus = useCallback5((increment, delta, cell) => {
1018
+ if (dxGrid && model) {
1019
+ if (cell?.plane === "grid" && cell?.row >= model.getRowCount() - 1) {
1020
+ if (draftRowCount < 1) {
1021
+ model.insertRow();
1022
+ }
1023
+ dxGrid.setFocus({
1024
+ plane: "frozenRowsEnd",
1025
+ col: 0,
1026
+ row: 0
1027
+ });
1028
+ } else if (cell?.plane === "frozenRowsEnd" && increment === "row") {
1029
+ handleSaveDraftRow(cell.row);
1030
+ model.insertRow();
1031
+ dxGrid.setFocus({
1032
+ plane: "frozenRowsEnd",
1033
+ col: 0,
1034
+ row: 0
2644
1035
  });
1036
+ } else {
1037
+ dxGrid.refocus(increment, delta);
2645
1038
  }
2646
1039
  }
2647
1040
  }, [
2648
- model,
2649
- dxGrid
1041
+ dxGrid,
1042
+ model
2650
1043
  ]);
2651
1044
  const handleKeyDown = useCallback5((event) => {
2652
1045
  if (!model?.features.dataEditable) {
@@ -2659,7 +1052,11 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
2659
1052
  switch (event.key) {
2660
1053
  case "Backspace":
2661
1054
  case "Delete": {
2662
- model.setCellData(cell, void 0);
1055
+ try {
1056
+ model.setCellData(cell, void 0);
1057
+ event.preventDefault();
1058
+ } catch {
1059
+ }
2663
1060
  break;
2664
1061
  }
2665
1062
  }
@@ -2693,10 +1090,10 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
2693
1090
  ]);
2694
1091
  const handleQuery = useCallback5(async ({ field, props }, text) => {
2695
1092
  if (model && props.referenceSchema && field.referencePath) {
2696
- const space = model.space;
2697
- invariant4(space, void 0, {
2698
- F: __dxlog_file5,
2699
- L: 325,
1093
+ const space = getSpace2(model.view);
1094
+ invariant3(space, void 0, {
1095
+ F: __dxlog_file3,
1096
+ L: 336,
2700
1097
  S: void 0,
2701
1098
  A: [
2702
1099
  "space",
@@ -2713,7 +1110,7 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
2713
1110
  if (schema2) {
2714
1111
  const { objects } = await space.db.query(Filter.type(schema2)).run();
2715
1112
  const options = objects.map((obj) => {
2716
- const value = getValue4(obj, field.referencePath);
1113
+ const value = getValue(obj, field.referencePath);
2717
1114
  if (!value || typeof value !== "string") {
2718
1115
  return void 0;
2719
1116
  }
@@ -2739,6 +1136,12 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
2739
1136
  client,
2740
1137
  t
2741
1138
  ]);
1139
+ const handleSave = useCallback5(() => {
1140
+ dxGrid?.updateCells(true);
1141
+ dxGrid?.requestUpdate();
1142
+ }, [
1143
+ dxGrid
1144
+ ]);
2742
1145
  if (!model || !modals) {
2743
1146
  return /* @__PURE__ */ React8.createElement("span", {
2744
1147
  role: "none",
@@ -2751,19 +1154,19 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
2751
1154
  model,
2752
1155
  modals,
2753
1156
  schema,
2754
- onEnter: handleEnter,
2755
1157
  onFocus: handleFocus,
2756
- onQuery: handleQuery
1158
+ onQuery: handleQuery,
1159
+ onSave: handleSave
2757
1160
  }), /* @__PURE__ */ React8.createElement(Grid.Content, {
2758
- className: mx2("[--dx-grid-base:var(--baseSurface)]", gridSeparatorInlineEnd, gridSeparatorBlockEnd),
1161
+ className: mx("[--dx-grid-base:var(--baseSurface)]", gridSeparatorInlineEnd, gridSeparatorBlockEnd),
2759
1162
  frozen,
2760
1163
  columns: model.columnMeta.value,
2761
1164
  limitRows: model.getRowCount() ?? 0,
2762
- limitColumns: model.view?.fields?.length ?? 0,
1165
+ limitColumns: model.projection.fields.length,
2763
1166
  overscroll: "trap",
2764
1167
  onAxisResize: handleAxisResize,
2765
1168
  onClick: handleGridClick,
2766
- onKeyDown: handleKeyDown,
1169
+ onKeyDownCapture: handleKeyDown,
2767
1170
  onWheelCapture: handleWheel,
2768
1171
  ref: setDxGrid
2769
1172
  }), /* @__PURE__ */ React8.createElement(RowActionsMenu, {
@@ -2790,141 +1193,38 @@ var Table = {
2790
1193
  };
2791
1194
 
2792
1195
  // src/components/Table/DynamicTable.tsx
2793
- import { useSignals as _useSignals10 } from "@preact-signals/safe-react/tracking";
2794
- import React9, { useRef as useRef3, useMemo as useMemo6, useCallback as useCallback7 } from "react";
2795
- import { useDefaultValue } from "@dxos/react-ui";
2796
- import { mx as mx3 } from "@dxos/react-ui-theme";
2797
-
2798
- // src/hooks/useTableModel.ts
2799
1196
  import { useSignals as _useSignals9 } from "@preact-signals/safe-react/tracking";
2800
- import { effect as effect3 } from "@preact/signals-core";
2801
- import orderBy2 from "lodash.orderby";
2802
- import { useEffect as useEffect5, useState as useState4 } from "react";
2803
- import { fullyQualifiedId, getSpace } from "@dxos/react-client/echo";
2804
- import { useSelectionActions } from "@dxos/react-ui-attention";
2805
- import { isNonNullable } from "@dxos/util";
2806
- var useTableModel = ({ table, projection, rows, rowActions, features, onSelectionChanged, onRowAction, ...props }) => {
1197
+ import React9, { useCallback as useCallback6, useMemo as useMemo6, useRef as useRef3 } from "react";
1198
+ import { useDefaultValue } from "@dxos/react-ui";
1199
+ import { mx as mx2 } from "@dxos/react-ui-theme";
1200
+ var DynamicTable = ({ classNames, name = "example.com/dynamic-table", rows, properties, jsonSchema: _jsonSchema, schema, rowActions, onRowClick, onRowAction, ...props }) => {
2807
1201
  var _effect = _useSignals9();
2808
1202
  try {
2809
- const [model, setModel] = useState4();
2810
- useEffect5(() => {
2811
- if (!table || !projection) {
2812
- return;
2813
- }
2814
- let model2;
2815
- const t = setTimeout(async () => {
2816
- model2 = new TableModel({
2817
- id: fullyQualifiedId(table),
2818
- space: getSpace(table),
2819
- view: table.view?.target,
2820
- projection,
2821
- features,
2822
- rowActions,
2823
- onRowAction,
2824
- ...props
2825
- });
2826
- await model2.open();
2827
- setModel(model2);
2828
- });
2829
- return () => {
2830
- clearTimeout(t);
2831
- void model2?.close();
2832
- };
2833
- }, [
2834
- table,
2835
- projection,
2836
- table?.view?.target,
2837
- features,
2838
- rowActions
2839
- ]);
2840
- useEffect5(() => {
2841
- if (rows) {
2842
- const sortedRows = orderBy2(rows, [
2843
- (row) => String(row.id)
2844
- ], [
2845
- "asc"
2846
- ]);
2847
- model?.setRows(sortedRows);
2848
- }
2849
- }, [
2850
- model,
2851
- rows
2852
- ]);
2853
- const { multiSelect, clear } = useSelectionActions([
2854
- table?.id,
2855
- table?.view?.target?.query.typename
2856
- ].filter(isNonNullable));
2857
- useEffect5(() => {
2858
- if (!model) {
2859
- return;
2860
- }
2861
- const unsubscribe = effect3(() => {
2862
- const selectedItems = [
2863
- ...model.selection.selection.value
2864
- ];
2865
- multiSelect(selectedItems);
2866
- onSelectionChanged?.(selectedItems);
2867
- });
2868
- return () => {
2869
- clear();
2870
- unsubscribe();
2871
- };
2872
- }, [
2873
- model,
2874
- onSelectionChanged
2875
- ]);
2876
- return model;
2877
- } finally {
2878
- _effect.f();
2879
- }
2880
- };
2881
-
2882
- // src/hooks/useAddRow.ts
2883
- import { useCallback as useCallback6 } from "react";
2884
- import { Obj as Obj3 } from "@dxos/echo";
2885
- var useAddRow = ({ space, schema }) => {
2886
- return useCallback6((data) => {
2887
- if (space && schema) {
2888
- try {
2889
- space.db.add(Obj3.make(schema, data ?? {}));
2890
- return true;
2891
- } catch (error) {
2892
- return false;
2893
- }
2894
- }
2895
- return false;
2896
- }, [
2897
- space,
2898
- schema
2899
- ]);
2900
- };
2901
-
2902
- // src/components/Table/DynamicTable.tsx
2903
- var DynamicTable = ({ classNames, name = "example.com/dynamic-table", rows, properties, jsonSchema, schema, rowActions, onRowClick, onRowAction, ...props }) => {
2904
- var _effect = _useSignals10();
2905
- try {
2906
- const { table, projection } = useMemo6(() => {
2907
- const props2 = getBaseSchema({
2908
- typename: name,
2909
- properties,
2910
- jsonSchema,
2911
- schema
2912
- });
2913
- return makeDynamicTable({
2914
- ...props2,
2915
- properties
2916
- });
2917
- }, [
1203
+ const { typename, jsonSchema } = useMemo6(() => getBaseSchema({
1204
+ typename: name,
1205
+ properties,
1206
+ jsonSchema: _jsonSchema,
1207
+ schema
1208
+ }), [
2918
1209
  name,
2919
1210
  properties,
2920
- schema,
2921
- jsonSchema
1211
+ _jsonSchema,
1212
+ schema
1213
+ ]);
1214
+ const { projection, view } = useMemo6(() => makeDynamicTable({
1215
+ typename,
1216
+ jsonSchema,
1217
+ properties
1218
+ }), [
1219
+ typename,
1220
+ jsonSchema,
1221
+ properties
2922
1222
  ]);
2923
1223
  const tableRef = useRef3(null);
2924
- const handleCellUpdate = useCallback7((cell) => {
1224
+ const handleCellUpdate = useCallback6((cell) => {
2925
1225
  tableRef.current?.update?.(cell);
2926
1226
  }, []);
2927
- const handleRowOrderChange = useCallback7(() => {
1227
+ const handleRowOrderChange = useCallback6(() => {
2928
1228
  tableRef.current?.update?.();
2929
1229
  }, []);
2930
1230
  const features = useDefaultValue(props.features, () => ({
@@ -2934,8 +1234,9 @@ var DynamicTable = ({ classNames, name = "example.com/dynamic-table", rows, prop
2934
1234
  dataEditable: false
2935
1235
  }));
2936
1236
  const model = useTableModel({
2937
- table,
2938
1237
  rows,
1238
+ view,
1239
+ schema: jsonSchema,
2939
1240
  projection,
2940
1241
  features,
2941
1242
  rowActions,
@@ -2952,7 +1253,7 @@ var DynamicTable = ({ classNames, name = "example.com/dynamic-table", rows, prop
2952
1253
  ]);
2953
1254
  return /* @__PURE__ */ React9.createElement("div", {
2954
1255
  role: "none",
2955
- className: mx3("is-full bs-full grow grid", classNames)
1256
+ className: mx2("is-full bs-full grow grid", classNames)
2956
1257
  }, /* @__PURE__ */ React9.createElement("div", {
2957
1258
  role: "none",
2958
1259
  className: "grid min-bs-0 overflow-hidden"
@@ -2969,11 +1270,11 @@ var DynamicTable = ({ classNames, name = "example.com/dynamic-table", rows, prop
2969
1270
  };
2970
1271
 
2971
1272
  // src/components/TableToolbar/TableToolbar.tsx
2972
- import { useSignals as _useSignals11 } from "@preact-signals/safe-react/tracking";
1273
+ import { useSignals as _useSignals10 } from "@preact-signals/safe-react/tracking";
2973
1274
  import { Rx } from "@effect-rx/rx-react";
2974
1275
  import React10, { useMemo as useMemo7 } from "react";
2975
- import { live as live3 } from "@dxos/live-object";
2976
- import { createGapSeparator, createMenuAction, MenuProvider, ToolbarMenu, useMenuActions } from "@dxos/react-ui-menu";
1276
+ import { live as live2 } from "@dxos/live-object";
1277
+ import { MenuProvider, ToolbarMenu, createGapSeparator, createMenuAction, useMenuActions } from "@dxos/react-ui-menu";
2977
1278
  import { rxFromSignal } from "@dxos/react-ui-menu";
2978
1279
  var createTableToolbarActions = ({ state, onAdd, onSave, customActions }) => Rx.make((get) => {
2979
1280
  const add = createMenuAction("add-row", onAdd, {
@@ -3021,9 +1322,9 @@ var createTableToolbarActions = ({ state, onAdd, onSave, customActions }) => Rx.
3021
1322
  };
3022
1323
  });
3023
1324
  var TableToolbar = ({ classNames, viewDirty, attendableId, onAdd, onSave, customActions }) => {
3024
- var _effect = _useSignals11();
1325
+ var _effect = _useSignals10();
3025
1326
  try {
3026
- const state = useMemo7(() => live3({
1327
+ const state = useMemo7(() => live2({
3027
1328
  viewDirty
3028
1329
  }), []);
3029
1330
  const actionsCreator = useMemo7(() => createTableToolbarActions({
@@ -3058,12 +1359,10 @@ export {
3058
1359
  Table,
3059
1360
  TableModel,
3060
1361
  TablePresentation,
3061
- TableSchema,
3062
1362
  TableToolbar,
3063
- TableType,
3064
1363
  cellClassesForRowSelection,
1364
+ createTable,
3065
1365
  getBaseSchema,
3066
- initializeTable,
3067
1366
  makeDynamicTable,
3068
1367
  narrowSchema,
3069
1368
  tableButtons,