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