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