@dxos/react-ui-table 0.8.3 → 0.8.4-main.28f8d3d

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