@dxos/react-ui-table 0.8.3 → 0.8.4-main.1f223c7

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 (148) hide show
  1. package/dist/lib/browser/chunk-63UUF4IG.mjs +66 -0
  2. package/dist/lib/browser/chunk-63UUF4IG.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-BBXDTWCX.mjs +1822 -0
  4. package/dist/lib/browser/chunk-BBXDTWCX.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +814 -2152
  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 +124 -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-K734DJB7.mjs +67 -0
  12. package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +7 -0
  13. package/dist/lib/node-esm/chunk-RNQDU7IQ.mjs +1823 -0
  14. package/dist/lib/node-esm/chunk-RNQDU7IQ.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +814 -2152
  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 +125 -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 +50 -2
  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 +44 -0
  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 +3 -2
  36. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  37. package/dist/types/src/components/Table/Table.stories.d.ts +42 -3
  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 +8 -3
  42. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  43. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +9 -15
  44. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  45. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +39 -3
  46. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  47. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
  48. package/dist/types/src/components/testing.d.ts +3 -40
  49. package/dist/types/src/components/testing.d.ts.map +1 -1
  50. package/dist/types/src/hooks/index.d.ts +1 -0
  51. package/dist/types/src/hooks/index.d.ts.map +1 -1
  52. package/dist/types/src/hooks/useAddRow.d.ts +14 -0
  53. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -0
  54. package/dist/types/src/hooks/useTableModel.d.ts +8 -7
  55. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  56. package/dist/types/src/index.d.ts +1 -2
  57. package/dist/types/src/index.d.ts.map +1 -1
  58. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  59. package/dist/types/src/model/selection-model.d.ts +2 -1
  60. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  61. package/dist/types/src/model/table-model.d.ts +59 -21
  62. package/dist/types/src/model/table-model.d.ts.map +1 -1
  63. package/dist/types/src/model/table-presentation.d.ts +10 -1
  64. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  65. package/dist/types/src/model/table-sorting.d.ts +3 -4
  66. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  67. package/dist/types/src/playwright/playwright.config.d.ts +3 -0
  68. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -0
  69. package/dist/types/src/testing/index.d.ts +2 -0
  70. package/dist/types/src/testing/index.d.ts.map +1 -0
  71. package/dist/types/src/testing/useTestTableModel.d.ts +22 -0
  72. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -0
  73. package/dist/types/src/translations.d.ts +5 -6
  74. package/dist/types/src/translations.d.ts.map +1 -1
  75. package/dist/types/src/types/index.d.ts +1 -1
  76. package/dist/types/src/types/index.d.ts.map +1 -1
  77. package/dist/types/src/types/schema.d.ts +28 -0
  78. package/dist/types/src/types/schema.d.ts.map +1 -0
  79. package/dist/types/src/util/dynamic-table.d.ts +4 -5
  80. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  81. package/dist/types/src/util/index.d.ts +0 -1
  82. package/dist/types/src/util/index.d.ts.map +1 -1
  83. package/dist/types/src/util/table-buttons.d.ts +15 -1
  84. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  85. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  86. package/dist/types/tsconfig.tsbuildinfo +1 -1
  87. package/package.json +57 -44
  88. package/src/components/Table/ColumnActionsMenu.tsx +1 -1
  89. package/src/components/Table/ColumnSettings.tsx +15 -11
  90. package/src/components/Table/CreateRefPanel.tsx +30 -11
  91. package/src/components/Table/DynamicTable.stories.tsx +8 -5
  92. package/src/components/Table/DynamicTable.tsx +17 -10
  93. package/src/components/Table/{relations.stories.tsx → Relations.stories.tsx} +56 -47
  94. package/src/components/Table/RowActionsMenu.tsx +3 -3
  95. package/src/components/Table/Table.stories.tsx +126 -159
  96. package/src/components/Table/Table.tsx +167 -123
  97. package/src/components/TableCellEditor/CellValidationMessage.tsx +7 -2
  98. package/src/components/TableCellEditor/FormCellEditor.tsx +158 -48
  99. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +31 -47
  100. package/src/components/TableCellEditor/TableCellEditor.tsx +106 -111
  101. package/src/components/TableToolbar/TableToolbar.tsx +2 -2
  102. package/src/components/testing.ts +7 -24
  103. package/src/hooks/index.ts +1 -0
  104. package/src/hooks/useAddRow.ts +38 -0
  105. package/src/hooks/useTableModel.ts +21 -17
  106. package/src/index.ts +1 -2
  107. package/src/model/modal-controller.ts +1 -1
  108. package/src/model/selection-model.ts +6 -3
  109. package/src/model/table-model.test.ts +12 -9
  110. package/src/model/table-model.ts +288 -88
  111. package/src/model/table-presentation.test.ts +12 -8
  112. package/src/model/table-presentation.ts +263 -138
  113. package/src/model/table-sorting.ts +22 -13
  114. package/src/playwright/playwright.config.ts +17 -0
  115. package/src/playwright/smoke.spec.ts +137 -44
  116. package/src/testing/index.ts +5 -0
  117. package/src/testing/useTestTableModel.ts +124 -0
  118. package/src/translations.ts +7 -5
  119. package/src/types/index.ts +2 -2
  120. package/src/types/schema.ts +73 -0
  121. package/src/util/dynamic-table.ts +24 -21
  122. package/src/util/index.ts +0 -1
  123. package/src/util/schema.ts +1 -1
  124. package/src/util/table-buttons.ts +28 -4
  125. package/src/util/table-controls.ts +4 -1
  126. package/dist/lib/browser/chunk-TL7CKQQV.mjs +0 -27
  127. package/dist/lib/browser/chunk-TL7CKQQV.mjs.map +0 -7
  128. package/dist/lib/node/chunk-O2VPBNS5.cjs +0 -50
  129. package/dist/lib/node/chunk-O2VPBNS5.cjs.map +0 -7
  130. package/dist/lib/node/index.cjs +0 -2739
  131. package/dist/lib/node/index.cjs.map +0 -7
  132. package/dist/lib/node/meta.json +0 -1
  133. package/dist/lib/node/types/index.cjs +0 -31
  134. package/dist/lib/node/types/index.cjs.map +0 -7
  135. package/dist/lib/node-esm/chunk-GBT6GD4G.mjs +0 -29
  136. package/dist/lib/node-esm/chunk-GBT6GD4G.mjs.map +0 -7
  137. package/dist/types/src/components/Table/relations.stories.d.ts +0 -9
  138. package/dist/types/src/components/Table/relations.stories.d.ts.map +0 -1
  139. package/dist/types/src/components/TableCellEditor/extension.d.ts +0 -13
  140. package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
  141. package/dist/types/src/types/table.d.ts +0 -56
  142. package/dist/types/src/types/table.d.ts.map +0 -1
  143. package/dist/types/src/util/table.d.ts +0 -14
  144. package/dist/types/src/util/table.d.ts.map +0 -1
  145. package/src/components/TableCellEditor/extension.ts +0 -61
  146. package/src/playwright/playwright.config.cts +0 -18
  147. package/src/types/table.ts +0 -27
  148. package/src/util/table.ts +0 -127
@@ -1,11 +1,26 @@
1
1
  import {
2
- TableSchema,
3
- TableType
4
- } from "./chunk-TL7CKQQV.mjs";
2
+ BUTTON_IDENTIFIERS,
3
+ CONTROL_IDENTIFIERS,
4
+ CheckboxStory,
5
+ ModalController,
6
+ SwitchStory,
7
+ TableModel,
8
+ TablePresentation,
9
+ cellClassesForRowSelection,
10
+ getBaseSchema,
11
+ makeDynamicTable,
12
+ narrowSchema,
13
+ tableButtons,
14
+ tableControls,
15
+ touch,
16
+ useAddRow,
17
+ useTableModel
18
+ } from "./chunk-BBXDTWCX.mjs";
19
+ import "./chunk-63UUF4IG.mjs";
5
20
 
6
- // packages/ui/react-ui-table/src/translations.ts
7
- var translationKey = "table";
8
- var translations_default = [
21
+ // src/translations.ts
22
+ var translationKey = "react-ui-table";
23
+ var translations = [
9
24
  {
10
25
  "en-US": {
11
26
  [translationKey]: {
@@ -13,8 +28,8 @@ var translations_default = [
13
28
  "create object label": "Create table",
14
29
  "table name placeholder": "Table name",
15
30
  "settings title": "Table settings",
16
- "table schema label": "Schema",
17
- "new schema": "New schema",
31
+ "table schema label": "Record type",
32
+ "new schema": "New record type",
18
33
  "continue label": "Continue",
19
34
  "create stack section label": "Create table",
20
35
  "add row label": "Add row",
@@ -35,1763 +50,158 @@ var translations_default = [
35
50
  }
36
51
  ];
37
52
 
38
- // packages/ui/react-ui-table/src/components/Table/Table.tsx
53
+ // src/components/Table/Table.tsx
39
54
  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";
55
+ import React8, { forwardRef, useCallback as useCallback5, useEffect as useEffect3, useImperativeHandle, useMemo as useMemo5, useState as useState4 } from "react";
45
56
  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";
57
+ import { Grid, closestCell, gridSeparatorBlockEnd, gridSeparatorInlineEnd } from "@dxos/react-ui-grid";
58
+ import { DxEditRequest } from "@dxos/react-ui-grid";
59
+ import { mx } from "@dxos/react-ui-theme";
60
+
61
+ // src/components/TableCellEditor/TableCellEditor.tsx
62
+ import { useSignals as _useSignals3 } from "@preact-signals/safe-react/tracking";
63
+ import { EditorView } from "@codemirror/view";
64
+ import React3, { useCallback as useCallback2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
65
+ import { debounce } from "@dxos/async";
66
+ import { FormatEnum, TypeEnum } from "@dxos/echo-schema";
67
+ import { invariant as invariant2 } from "@dxos/invariant";
68
+ import { createMarkdownExtensions } from "@dxos/react-ui-editor";
69
+ import { GridCellEditor, editorKeys, parseCellIndex as parseCellIndex2, useGridContext as useGridContext3 } from "@dxos/react-ui-grid";
70
+ import { createLinks, tagPicker } from "@dxos/react-ui-tag-picker";
49
71
 
50
- // packages/ui/react-ui-table/src/components/Table/ColumnActionsMenu.tsx
72
+ // src/components/TableCellEditor/CellValidationMessage.tsx
51
73
  import { useSignals as _useSignals } from "@preact-signals/safe-react/tracking";
52
74
  import React from "react";
53
- import { DropdownMenu, useTranslation } from "@dxos/react-ui";
54
- var ColumnActionsMenu = ({ model, modals }) => {
75
+ import { useGridContext } from "@dxos/react-ui-grid";
76
+ var CellValidationMessage = ({ validationError, variant = "error", __gridScope }) => {
55
77
  var _effect = _useSignals();
56
78
  try {
57
- const { t } = useTranslation(translationKey);
58
- const state = modals.state.value;
59
- if (state?.type !== "column") {
79
+ const { editing, editBox: box } = useGridContext("GridSheetCellEditor", __gridScope);
80
+ if (!editing || !validationError) {
60
81
  return null;
61
82
  }
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))));
83
+ const bgClass = variant === "error" ? "bg-errorSurface" : "bg-warningSurface";
84
+ const textClass = variant === "error" ? "text-errorSurfaceText" : "text-warningSurfaceText";
85
+ return /* @__PURE__ */ React.createElement("div", {
86
+ role: "none",
87
+ className: `absolute ${bgClass} ${textClass} rounded-bs-sm text-xs p-1`,
88
+ style: {
89
+ ...{
90
+ "--dx-gridCellWidth": `${box?.inlineSize ?? 200}px`
91
+ },
92
+ zIndex: 10,
93
+ insetBlockEnd: `calc(100% - ${box.insetBlockStart}px)`,
94
+ insetInlineStart: box.insetInlineStart,
95
+ inlineSize: box.inlineSize
96
+ }
97
+ }, validationError);
86
98
  } finally {
87
99
  _effect.f();
88
100
  }
89
101
  };
90
102
 
91
- // packages/ui/react-ui-table/src/components/Table/ColumnSettings.tsx
103
+ // src/components/TableCellEditor/FormCellEditor.tsx
92
104
  import { useSignals as _useSignals2 } from "@preact-signals/safe-react/tracking";
93
- import React2, { useCallback, useEffect, useMemo, useState } from "react";
105
+ import React2, { useCallback, useEffect, useMemo, useRef, useState } from "react";
106
+ import { Filter, Obj } from "@dxos/echo";
107
+ import { Ref, getValue } from "@dxos/echo-schema";
108
+ import { invariant } from "@dxos/invariant";
109
+ import { getSnapshot } from "@dxos/live-object";
110
+ import { getSpace } from "@dxos/react-client/echo";
94
111
  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
112
  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();
113
+ import { cellQuery, parseCellIndex, useGridContext as useGridContext2 } from "@dxos/react-ui-grid";
114
+ import { getDeep, isNotFalsy, setDeep } from "@dxos/util";
115
+ var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-table/src/components/TableCellEditor/FormCellEditor.tsx";
116
+ var createOptionLabel = [
117
+ "create new object label",
118
+ {
119
+ ns: translationKey
120
+ }
121
+ ];
122
+ var FormCellEditor = ({ fieldProjection, model, schema, onSave, client, modals, __gridScope, ...formProps }) => {
123
+ var _effect = _useSignals2();
175
124
  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;
125
+ const { editing: contextEditing, setEditing, id: gridId } = useGridContext2("ArrayEditor", __gridScope);
126
+ const [editing, setLocalEditing] = useState(false);
127
+ const anchorRef = useRef(null);
128
+ const getSchema = useCallback((typeAnnotation) => {
129
+ const space = getSpace(model.view);
130
+ invariant(space, void 0, {
131
+ F: __dxlog_file,
132
+ L: 53,
133
+ S: void 0,
134
+ A: [
135
+ "space",
136
+ ""
137
+ ]
138
+ });
139
+ let schema2;
140
+ if (client) {
141
+ schema2 = client.graph.schemaRegistry.getSchema(typeAnnotation.typename);
197
142
  }
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);
143
+ if (!schema2) {
144
+ schema2 = space.db.schemaRegistry.getSchema(typeAnnotation.typename);
204
145
  }
205
- void modals.close();
146
+ return {
147
+ space,
148
+ schema: schema2
149
+ };
206
150
  }, [
207
- modals,
208
- space,
209
- state
151
+ client,
152
+ model
210
153
  ]);
211
- const handleCancel = useCallback2(() => {
212
- if (model) {
213
- modals.close();
154
+ const handleQueryRefOptions = useCallback(async (typeAnnotation) => {
155
+ const { schema: schema2, space } = getSchema(typeAnnotation);
156
+ if (model && schema2 && space) {
157
+ const { objects } = await space.db.query(Filter.type(schema2)).run();
158
+ return objects.map((obj) => {
159
+ return {
160
+ dxn: Obj.getDXN(obj),
161
+ label: getValue(obj, fieldProjection.field.referencePath) || obj.id.toString()
162
+ };
163
+ }).filter(isNotFalsy);
214
164
  }
165
+ return [];
215
166
  }, [
216
- modals
167
+ client,
168
+ model
217
169
  ]);
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();
170
+ const originalRow = useMemo(() => {
171
+ if (model && contextEditing) {
172
+ const cell = parseCellIndex(contextEditing.index);
173
+ if (model.isDraftCell(cell)) {
174
+ const draftRow = model.draftRows.value[cell.row];
175
+ invariant(draftRow, void 0, {
176
+ F: __dxlog_file,
177
+ L: 94,
178
+ S: void 0,
179
+ A: [
180
+ "draftRow",
181
+ ""
182
+ ]
183
+ });
184
+ return draftRow.data;
185
+ } else {
186
+ const row = model.getRowAt(cell.row);
187
+ invariant(row, void 0, {
188
+ F: __dxlog_file,
189
+ L: 98,
190
+ S: void 0,
191
+ A: [
192
+ "row",
193
+ ""
194
+ ]
195
+ });
196
+ return row;
227
197
  }
228
198
  }
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
- };
1367
-
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: ""
1668
- }
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
- }
1681
- }
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
- };
1694
-
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";
1699
- import { invariant as invariant3 } from "@dxos/invariant";
1700
- import { useThemeContext } from "@dxos/react-ui";
1701
- 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";
1704
-
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();
1711
- try {
1712
- const { editing, editBox: box } = useGridContext2("GridSheetCellEditor", __gridScope);
1713
- if (!editing || !validationError) {
1714
- return null;
1715
- }
1716
- return /* @__PURE__ */ React5.createElement("div", {
1717
- role: "none",
1718
- className: "absolute bg-errorSurface text-errorSurfaceText rounded-bs-sm text-xs p-1",
1719
- style: {
1720
- ...{
1721
- "--dx-gridCellWidth": `${box?.inlineSize ?? 200}px`
1722
- },
1723
- zIndex: 10,
1724
- insetBlockEnd: `calc(100% - ${box.insetBlockStart}px)`,
1725
- insetInlineStart: box.insetInlineStart,
1726
- inlineSize: box.inlineSize
1727
- }
1728
- }, validationError);
1729
- } finally {
1730
- _effect.f();
1731
- }
1732
- };
1733
-
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
199
  return void 0;
1783
200
  }, [
1784
201
  model,
1785
- editing
1786
- ]);
1787
- const formValues = useMemo3(() => {
1788
- if (originalRow) {
1789
- return getSnapshot2(originalRow);
1790
- }
1791
- }, [
1792
- originalRow
202
+ contextEditing
1793
203
  ]);
1794
- const handleSave = useCallback3((values) => {
204
+ const handleSave = useCallback((values) => {
1795
205
  const path = fieldProjection.field.path;
1796
206
  const value = getDeep(values, [
1797
207
  path
@@ -1799,100 +209,124 @@ var FormCellEditor = ({ fieldProjection, model, schema, __gridScope }) => {
1799
209
  setDeep(originalRow, [
1800
210
  path
1801
211
  ], value);
212
+ contextEditing?.cellElement?.focus();
1802
213
  setEditing(null);
214
+ setLocalEditing(false);
215
+ onSave?.();
1803
216
  }, [
1804
217
  fieldProjection.field.path,
218
+ onSave,
219
+ contextEditing,
1805
220
  originalRow
1806
221
  ]);
1807
- const handleOpenChange = useCallback3((nextOpen) => {
1808
- if (nextOpen === false) {
1809
- setEditing(null);
222
+ const handleCreate = useCallback((object) => {
223
+ const ref = Ref.make(object);
224
+ const path = fieldProjection.field.path;
225
+ setDeep(originalRow, [
226
+ path
227
+ ], ref);
228
+ contextEditing?.cellElement?.focus();
229
+ setEditing(null);
230
+ setLocalEditing(false);
231
+ onSave?.();
232
+ }, [
233
+ fieldProjection.field.path,
234
+ onSave,
235
+ contextEditing,
236
+ originalRow
237
+ ]);
238
+ const handleCreateFromQuery = useCallback(async (typeAnnotation, query) => {
239
+ if (model && modals && contextEditing?.index) {
240
+ setLocalEditing(false);
241
+ modals.openCreateRef(typeAnnotation.typename, document.querySelector(cellQuery(contextEditing.index, gridId)), {
242
+ [fieldProjection.field.referencePath]: query
243
+ }, handleCreate);
1810
244
  }
1811
245
  }, [
1812
- setEditing
246
+ model,
247
+ modals,
248
+ client,
249
+ contextEditing?.index,
250
+ gridId,
251
+ fieldProjection,
252
+ handleSave
253
+ ]);
254
+ useEffect(() => {
255
+ if (contextEditing && contextEditing.cellElement) {
256
+ anchorRef.current = contextEditing.cellElement.querySelector(".dx-grid__cell__content");
257
+ setLocalEditing(true);
258
+ } else {
259
+ anchorRef.current = null;
260
+ setLocalEditing(false);
261
+ }
262
+ }, [
263
+ contextEditing
264
+ ]);
265
+ const narrowedSchema = useMemo(() => {
266
+ if (!schema) {
267
+ return void 0;
268
+ }
269
+ return narrowSchema(schema, [
270
+ fieldProjection.field.path
271
+ ]);
272
+ }, [
273
+ JSON.stringify(schema),
274
+ fieldProjection.field.path
275
+ ]);
276
+ const formValues = useMemo(() => originalRow ? getSnapshot(originalRow) : {}, [
277
+ originalRow
1813
278
  ]);
279
+ const handleOpenChange = useCallback((nextOpen) => {
280
+ if (nextOpen === false) {
281
+ setEditing(null);
282
+ onSave?.();
283
+ }
284
+ setLocalEditing(nextOpen);
285
+ }, []);
1814
286
  if (!editing) {
1815
287
  return null;
1816
288
  }
1817
- return /* @__PURE__ */ React6.createElement(Popover3.Root, {
1818
- open: editing !== null,
289
+ return /* @__PURE__ */ React2.createElement(Popover.Root, {
290
+ open: editing,
1819
291
  onOpenChange: handleOpenChange
1820
- }, /* @__PURE__ */ React6.createElement(Popover3.VirtualTrigger, {
1821
- virtualRef: cellRef
1822
- }), /* @__PURE__ */ React6.createElement(Popover3.Content, {
292
+ }, /* @__PURE__ */ React2.createElement(Popover.VirtualTrigger, {
293
+ virtualRef: anchorRef
294
+ }), /* @__PURE__ */ React2.createElement(Popover.Portal, null, /* @__PURE__ */ React2.createElement(Popover.Content, {
1823
295
  tabIndex: -1,
1824
- classNames: "popover-card-width"
1825
- }, /* @__PURE__ */ React6.createElement(Popover3.Arrow, null), /* @__PURE__ */ React6.createElement(Popover3.Viewport, null, /* @__PURE__ */ React6.createElement(Form2, {
296
+ classNames: "popover-card-width density-fine"
297
+ }, /* @__PURE__ */ React2.createElement(Popover.Arrow, null), /* @__PURE__ */ React2.createElement(Popover.Viewport, null, /* @__PURE__ */ React2.createElement(Form, {
298
+ autoFocus: true,
1826
299
  values: formValues,
1827
300
  schema: narrowedSchema,
1828
- onSave: handleSave
1829
- }))));
301
+ onSave: handleSave,
302
+ ...formProps,
303
+ onQueryRefOptions: handleQueryRefOptions,
304
+ onCreateFromQuery: handleCreateFromQuery,
305
+ createOptionIcon: "ph--plus--regular",
306
+ createOptionLabel
307
+ })))));
1830
308
  } finally {
1831
309
  _effect.f();
1832
310
  }
1833
311
  };
1834
312
 
1835
- // packages/ui/react-ui-table/src/components/TableCellEditor/extension.ts
1836
- import { autocompletion, pickedCompletion } from "@codemirror/autocomplete";
1837
- import { EditorView } from "@codemirror/view";
1838
- var completion = ({ debug, onQuery, onMatch }) => {
1839
- return [
1840
- EditorView.theme({
1841
- ".cm-completionDialog": {
1842
- width: "var(--dx-gridCellWidth)"
1843
- }
1844
- }),
1845
- autocompletion({
1846
- icons: false,
1847
- activateOnTyping: true,
1848
- closeOnBlur: !debug,
1849
- override: [
1850
- async (context) => {
1851
- const text = context.state.doc.toString();
1852
- return {
1853
- from: 0,
1854
- options: await onQuery(text)
1855
- };
1856
- }
1857
- ],
1858
- // TODO(burdon): Consts from grid (to line-up with grid).
1859
- tooltipClass: () => "cm-completionDialog !mt-[8px] !-ml-[4px] [&>ul]:!max-h-[264px]",
1860
- optionClass: () => "flex h-[33px] items-center"
1861
- }),
1862
- // Check for accepted completion metadata in the transaction.
1863
- EditorView.updateListener.of((update) => {
1864
- update.transactions.forEach((tr) => {
1865
- const completion2 = tr.annotation(pickedCompletion);
1866
- if (completion2) {
1867
- onMatch(completion2.data);
1868
- }
1869
- });
1870
- })
1871
- ];
1872
- };
1873
-
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";
1876
- var newValue = Symbol.for("newValue");
1877
- var createOption = (text) => ({
1878
- [newValue]: true,
1879
- text
1880
- });
1881
- var isCreateOption = (data) => typeof data === "object" && data[newValue];
1882
- var TableValueEditor = ({ model, modals, schema, onEnter, onFocus, onQuery, __gridScope }) => {
1883
- var _effect = _useSignals7();
313
+ // src/components/TableCellEditor/TableCellEditor.tsx
314
+ var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/react-ui-table/src/components/TableCellEditor/TableCellEditor.tsx";
315
+ var adaptValidationMessage = (message) => message ? message.endsWith("is missing") ? "Can\u2019t be blank" : message : null;
316
+ var TableValueEditor = ({ model, modals, schema, onFocus, onSave, client, __gridScope }) => {
317
+ var _effect = _useSignals3();
1884
318
  try {
1885
- const { editing } = useGridContext4("TableValueEditor", __gridScope);
1886
- const fieldProjection = useMemo4(() => {
319
+ const { editing } = useGridContext3("TableValueEditor", __gridScope);
320
+ const fieldProjection = useMemo2(() => {
1887
321
  if (!model || !editing) {
1888
322
  return;
1889
323
  }
1890
324
  const { col } = parseCellIndex2(editing.index);
1891
- const field = model.projection.view.fields[col];
325
+ const field = model.projection.fields[col];
1892
326
  const fieldProjection2 = model.projection.getFieldProjection(field.id);
1893
- invariant3(fieldProjection2, void 0, {
1894
- F: __dxlog_file4,
1895
- L: 74,
327
+ invariant2(fieldProjection2, void 0, {
328
+ F: __dxlog_file2,
329
+ L: 71,
1896
330
  S: void 0,
1897
331
  A: [
1898
332
  "fieldProjection",
@@ -1904,43 +338,50 @@ var TableValueEditor = ({ model, modals, schema, onEnter, onFocus, onQuery, __gr
1904
338
  model,
1905
339
  editing
1906
340
  ]);
1907
- if (fieldProjection?.props.type === TypeEnum4.Array) {
1908
- return /* @__PURE__ */ React7.createElement(FormCellEditor, {
341
+ if (fieldProjection?.props.type === TypeEnum.Array || fieldProjection?.props.format === FormatEnum.SingleSelect || fieldProjection?.props.format === FormatEnum.Ref) {
342
+ return /* @__PURE__ */ React3.createElement(FormCellEditor, {
1909
343
  fieldProjection,
1910
344
  model,
1911
345
  schema,
1912
- __gridScope
346
+ __gridScope,
347
+ onSave,
348
+ client,
349
+ modals
1913
350
  });
1914
351
  }
1915
- return /* @__PURE__ */ React7.createElement(TableCellEditor, {
352
+ return /* @__PURE__ */ React3.createElement(TableCellEditor, {
1916
353
  model,
1917
354
  modals,
1918
- onEnter,
1919
355
  onFocus,
1920
- onQuery,
356
+ onSave,
1921
357
  __gridScope
1922
358
  });
1923
359
  } finally {
1924
360
  _effect.f();
1925
361
  }
1926
362
  };
1927
- var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }) => {
1928
- var _effect = _useSignals7();
363
+ var editorSlots = {
364
+ scroll: {
365
+ className: "!plb-[--dx-grid-cell-editor-padding-block]"
366
+ }
367
+ };
368
+ var TableCellEditor = ({ model, modals, onFocus, onSave, __gridScope }) => {
369
+ var _effect = _useSignals3();
1929
370
  try {
1930
- const { id: gridId, editing, setEditing } = useGridContext4("TableCellEditor", __gridScope);
371
+ const { editing, setEditing } = useGridContext3("TableCellEditor", __gridScope);
1931
372
  const suppressNextBlur = useRef2(false);
1932
- const { themeMode } = useThemeContext();
1933
- const [_validationError, setValidationError] = useState2(null);
1934
- const fieldProjection = useMemo4(() => {
373
+ const [validationError, setValidationError] = useState2(null);
374
+ const [validationVariant, setValidationVariant] = useState2("error");
375
+ const fieldProjection = useMemo2(() => {
1935
376
  if (!model || !editing) {
1936
377
  return;
1937
378
  }
1938
379
  const { col } = parseCellIndex2(editing.index);
1939
- const field = model.projection.view.fields[col];
380
+ const field = model.projection.fields[col];
1940
381
  const fieldProjection2 = model.projection.getFieldProjection(field.id);
1941
- invariant3(fieldProjection2, void 0, {
1942
- F: __dxlog_file4,
1943
- L: 116,
382
+ invariant2(fieldProjection2, void 0, {
383
+ F: __dxlog_file2,
384
+ L: 120,
1944
385
  S: void 0,
1945
386
  A: [
1946
387
  "fieldProjection",
@@ -1952,40 +393,48 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
1952
393
  model,
1953
394
  editing
1954
395
  ]);
1955
- const handleEnter = useCallback4(async (value) => {
396
+ const handleEnter = useCallback2(async (value) => {
1956
397
  if (!model || !editing) {
1957
398
  return;
1958
399
  }
1959
400
  const cell = parseCellIndex2(editing.index);
1960
- const result = await model.validateCellData(cell, value);
1961
- if (result.valid) {
401
+ const validationResult = await model.validateCellData(cell, value);
402
+ if (validationResult.valid) {
1962
403
  setValidationError(null);
1963
404
  model.setCellData(cell, value);
1964
- onEnter?.(cell);
1965
405
  onFocus?.();
1966
406
  setEditing(null);
407
+ onSave?.();
1967
408
  } else {
1968
- setValidationError(result.error);
409
+ setValidationError(adaptValidationMessage(validationResult.error));
410
+ setValidationVariant("error");
1969
411
  }
1970
412
  }, [
1971
413
  model,
1972
414
  editing,
1973
- onEnter,
1974
415
  onFocus,
1975
416
  setEditing
1976
417
  ]);
1977
- const handleBlur = useCallback4(async (value) => {
1978
- if (!model || !editing) {
1979
- return;
1980
- }
418
+ const handleBlur = useCallback2(async (value) => {
1981
419
  if (suppressNextBlur.current) {
1982
420
  suppressNextBlur.current = false;
421
+ } else if (model && editing) {
422
+ const cell = parseCellIndex2(editing.index);
423
+ if (value !== void 0) {
424
+ const result = await model.validateCellData(cell, value);
425
+ if (result.valid) {
426
+ setValidationError(null);
427
+ model.setCellData(cell, value);
428
+ setEditing(null);
429
+ onSave?.();
430
+ }
431
+ }
1983
432
  }
1984
433
  }, [
1985
434
  model,
1986
435
  editing
1987
436
  ]);
1988
- const handleClose = useCallback4(async (value, event) => {
437
+ const handleClose = useCallback2(async (value, event) => {
1989
438
  if (!model || !editing || !fieldProjection) {
1990
439
  return;
1991
440
  }
@@ -1993,19 +442,23 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
1993
442
  if (value !== void 0) {
1994
443
  const result = await model.validateCellData(cell, value);
1995
444
  if (result.valid) {
445
+ suppressNextBlur.current = true;
1996
446
  setValidationError(null);
1997
447
  model.setCellData(cell, value);
1998
448
  setEditing(null);
1999
- onEnter?.(cell);
449
+ onSave?.();
2000
450
  if (event && onFocus) {
2001
- onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
451
+ onFocus(determineNavigationAxis(event), determineNavigationDelta(event), cell);
2002
452
  }
2003
453
  } else {
2004
454
  setValidationError(result.error);
455
+ setValidationVariant("error");
2005
456
  }
2006
457
  } else {
458
+ suppressNextBlur.current = true;
2007
459
  setValidationError(null);
2008
460
  setEditing(null);
461
+ onSave?.();
2009
462
  if (event && onFocus) {
2010
463
  onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
2011
464
  }
@@ -2014,15 +467,15 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
2014
467
  model,
2015
468
  editing,
2016
469
  onFocus,
2017
- onEnter,
2018
470
  fieldProjection,
2019
- setEditing
471
+ setEditing,
472
+ onSave
2020
473
  ]);
2021
- const extension = useMemo4(() => {
474
+ const extensions = useMemo2(() => {
2022
475
  if (!fieldProjection) {
2023
476
  return [];
2024
477
  }
2025
- const extension2 = [
478
+ const extensions2 = [
2026
479
  editorKeys({
2027
480
  onClose: handleClose,
2028
481
  ...editing?.initialContent && {
@@ -2031,15 +484,13 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
2031
484
  })
2032
485
  ];
2033
486
  const format = fieldProjection.props.format;
2034
- if (format === FormatEnum5.SingleSelect || format === FormatEnum5.MultiSelect) {
2035
- extension2.push(createMarkdownExtensions({
2036
- themeMode
2037
- }));
487
+ if (format === FormatEnum.SingleSelect || format === FormatEnum.MultiSelect) {
2038
488
  const options = fieldProjection.props.options || [];
2039
- const mode = format === FormatEnum5.SingleSelect ? "single-select" : "multi-select";
2040
- extension2.push(tagPickerExtension({
489
+ const mode = format === FormatEnum.SingleSelect ? "single-select" : "multi-select";
490
+ extensions2.push(createMarkdownExtensions());
491
+ extensions2.push(tagPicker({
2041
492
  mode,
2042
- inGrid: true,
493
+ keymap: false,
2043
494
  onSearch: (text, selectedIds) => {
2044
495
  return options.filter((option) => selectedIds.indexOf(option.id) === -1 && (text.length === 0 || option.title.toLowerCase().includes(text.toLowerCase()))).map((option) => ({
2045
496
  id: option.id,
@@ -2061,135 +512,381 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
2061
512
  }
2062
513
  }));
2063
514
  }
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
- }
515
+ if (model && editing) {
516
+ extensions2.push(EditorView.updateListener.of(debounce((update) => {
517
+ const content = update.state.doc.toString();
518
+ const cell = parseCellIndex2(editing.index);
519
+ void model.validateCellData(cell, content).then((result) => {
520
+ if (result.valid) {
521
+ setValidationError(null);
522
+ } else {
523
+ setValidationError(result.error);
524
+ setValidationVariant("error");
525
+ }
526
+ });
527
+ }, 10)));
528
+ }
529
+ return extensions2;
530
+ }, [
531
+ model,
532
+ modals,
533
+ editing,
534
+ fieldProjection,
535
+ handleClose
536
+ ]);
537
+ const getCellContent = useCallback2(() => {
538
+ if (model && editing) {
539
+ const cell = parseCellIndex2(editing.index);
540
+ const { col } = cell;
541
+ const field = model.projection.fields[col];
542
+ const fieldProjection2 = model.projection.getFieldProjection(field.id);
543
+ if (fieldProjection2?.props.format === FormatEnum.SingleSelect || fieldProjection2?.props.format === FormatEnum.MultiSelect) {
544
+ const value2 = model.getCellData(cell);
545
+ if (value2 !== void 0) {
546
+ const options = fieldProjection2.props.options || [];
547
+ if (fieldProjection2.props.format === FormatEnum.MultiSelect) {
548
+ const tagItems = value2.split(",").map((id) => {
549
+ const option = options.find((o) => o.id === id);
550
+ if (option) {
551
+ return {
552
+ id,
553
+ label: option.title,
554
+ hue: option.color
555
+ };
556
+ }
557
+ return void 0;
558
+ }).filter((item) => item !== void 0);
559
+ return createLinks(tagItems);
560
+ } else {
561
+ const option = options.find((o) => o.id === value2);
562
+ if (option) {
563
+ const tagItem = {
564
+ id: value2,
565
+ label: option.title,
566
+ hue: option.color
567
+ };
568
+ return createLinks([
569
+ tagItem
570
+ ]);
571
+ }
572
+ }
573
+ }
574
+ return "";
575
+ }
576
+ const value = model.getCellData(cell);
577
+ return value !== void 0 ? String(value) : "";
578
+ }
579
+ }, [
580
+ model,
581
+ editing
582
+ ]);
583
+ return /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(CellValidationMessage, {
584
+ validationError,
585
+ variant: validationVariant,
586
+ __gridScope
587
+ }), /* @__PURE__ */ React3.createElement(GridCellEditor, {
588
+ extensions,
589
+ getCellContent,
590
+ onBlur: handleBlur,
591
+ slots: editorSlots
592
+ }));
593
+ } finally {
594
+ _effect.f();
595
+ }
596
+ };
597
+ var determineNavigationAxis = ({ key }) => {
598
+ switch (key) {
599
+ case "ArrowUp":
600
+ case "ArrowDown":
601
+ case "Enter": {
602
+ return "row";
603
+ }
604
+ case "ArrowLeft":
605
+ case "ArrowRight":
606
+ case "Tab":
607
+ return "col";
608
+ }
609
+ };
610
+ var determineNavigationDelta = ({ key, shift }) => {
611
+ switch (key) {
612
+ case "ArrowUp":
613
+ case "ArrowLeft":
614
+ return -1;
615
+ case "ArrowDown":
616
+ case "ArrowRight":
617
+ return 1;
618
+ }
619
+ return shift ? -1 : 1;
620
+ };
621
+
622
+ // src/components/Table/ColumnActionsMenu.tsx
623
+ import { useSignals as _useSignals4 } from "@preact-signals/safe-react/tracking";
624
+ import React4 from "react";
625
+ import { DropdownMenu, useTranslation } from "@dxos/react-ui";
626
+ var ColumnActionsMenu = ({ model, modals }) => {
627
+ var _effect = _useSignals4();
628
+ try {
629
+ const { t } = useTranslation(translationKey);
630
+ const state = modals.state.value;
631
+ if (state?.type !== "column") {
632
+ return null;
633
+ }
634
+ const currentSort = model.sorting?.sorting;
635
+ const isCurrentColumnSorted = currentSort?.fieldId === state.fieldId;
636
+ return /* @__PURE__ */ React4.createElement(DropdownMenu.Root, {
637
+ modal: false,
638
+ open: true,
639
+ onOpenChange: modals.close
640
+ }, /* @__PURE__ */ React4.createElement(DropdownMenu.VirtualTrigger, {
641
+ virtualRef: modals.trigger
642
+ }), /* @__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, {
643
+ "data-testid": "column-sort-descending",
644
+ onClick: () => model.sorting?.setSort(state.fieldId, "desc")
645
+ }, t("column action sort descending")), (!isCurrentColumnSorted || currentSort?.direction === "desc") && /* @__PURE__ */ React4.createElement(DropdownMenu.Item, {
646
+ "data-testid": "column-sort-ascending",
647
+ onClick: () => model.sorting?.setSort(state.fieldId, "asc")
648
+ }, t("column action sort ascending")), isCurrentColumnSorted && /* @__PURE__ */ React4.createElement(DropdownMenu.Item, {
649
+ "data-testid": "column-clear-sort",
650
+ onClick: () => model.sorting?.clearSort()
651
+ }, t("column action clear sorting")), model.getColumnCount() > 1 && model.features.schemaEditable && /* @__PURE__ */ React4.createElement(DropdownMenu.Item, {
652
+ "data-testid": "column-delete",
653
+ onClick: () => model.deleteColumn(state.fieldId)
654
+ }, t("column action delete")), model.features.schemaEditable && /* @__PURE__ */ React4.createElement(DropdownMenu.Item, {
655
+ "data-testid": "column-settings",
656
+ onClick: () => modals.openColumnSettings()
657
+ }, t("column action settings"))), /* @__PURE__ */ React4.createElement(DropdownMenu.Arrow, null))));
658
+ } finally {
659
+ _effect.f();
660
+ }
661
+ };
662
+
663
+ // src/components/Table/ColumnSettings.tsx
664
+ import { useSignals as _useSignals5 } from "@preact-signals/safe-react/tracking";
665
+ import React5, { useCallback as useCallback3, useEffect as useEffect2, useMemo as useMemo3, useState as useState3 } from "react";
666
+ import { Popover as Popover2 } from "@dxos/react-ui";
667
+ import { FieldEditor } from "@dxos/react-ui-form";
668
+ var ColumnSettings = ({ registry, model, modals, onNewColumn }) => {
669
+ var _effect = _useSignals5();
670
+ try {
671
+ const [newField, setNewField] = useState3();
672
+ const state = modals.state.value;
673
+ useEffect2(() => {
674
+ if (state?.type === "columnSettings" && state.mode.type === "create" && model?.projection) {
675
+ setNewField(model.projection.createFieldProjection());
676
+ requestAnimationFrame(() => {
677
+ onNewColumn();
678
+ });
679
+ } else {
680
+ setNewField(void 0);
681
+ }
682
+ }, [
683
+ model?.projection,
684
+ state
685
+ ]);
686
+ const existingField = useMemo3(() => {
687
+ if (state?.type === "columnSettings") {
688
+ const { mode } = state;
689
+ if (mode.type === "edit") {
690
+ return model?.projection?.fields.find((f) => f.id === mode.fieldId);
691
+ }
692
+ }
693
+ return void 0;
694
+ }, [
695
+ model?.projection?.fields,
696
+ state
697
+ ]);
698
+ const field = existingField ?? newField;
699
+ const handleSave = useCallback3(() => {
700
+ modals.close();
701
+ }, [
702
+ modals
703
+ ]);
704
+ const handleCancel = useCallback3(() => {
705
+ if (state?.type === "columnSettings" && state.mode.type === "create" && newField) {
706
+ model?.projection.deleteFieldProjection(newField.id);
707
+ }
708
+ }, [
709
+ model?.projection,
710
+ state,
711
+ newField
712
+ ]);
713
+ if (!model?.projection || !field) {
714
+ return null;
715
+ }
716
+ return /* @__PURE__ */ React5.createElement(Popover2.Root, {
717
+ modal: false,
718
+ open: state?.type === "columnSettings"
719
+ }, /* @__PURE__ */ React5.createElement(Popover2.VirtualTrigger, {
720
+ virtualRef: modals.trigger
721
+ }), /* @__PURE__ */ React5.createElement(Popover2.Portal, null, /* @__PURE__ */ React5.createElement(Popover2.Content, {
722
+ classNames: "md:is-64"
723
+ }, /* @__PURE__ */ React5.createElement(Popover2.Viewport, null, /* @__PURE__ */ React5.createElement(FieldEditor, {
724
+ projection: model.projection,
725
+ field,
726
+ registry,
727
+ onSave: handleSave,
728
+ onCancel: handleCancel
729
+ })), /* @__PURE__ */ React5.createElement(Popover2.Arrow, null))));
730
+ } finally {
731
+ _effect.f();
732
+ }
733
+ };
734
+
735
+ // src/components/Table/CreateRefPanel.tsx
736
+ import { useSignals as _useSignals6 } from "@preact-signals/safe-react/tracking";
737
+ import { Schema } from "effect";
738
+ import React6, { useCallback as useCallback4, useMemo as useMemo4 } from "react";
739
+ import { Type } from "@dxos/echo";
740
+ import { live } from "@dxos/live-object";
741
+ import { useClient } from "@dxos/react-client";
742
+ import { getSpace as getSpace2 } from "@dxos/react-client/echo";
743
+ import { Popover as Popover3 } from "@dxos/react-ui";
744
+ import { Form as Form2 } from "@dxos/react-ui-form";
745
+ import { useGridContext as useGridContext4 } from "@dxos/react-ui-grid";
746
+ var CreateRefPanel = ({ model, modals, __gridScope }) => {
747
+ var _effect = _useSignals6();
748
+ try {
749
+ const { id: gridId } = useGridContext4("TableCellEditor", __gridScope);
750
+ const client = useClient();
751
+ const space = model && getSpace2(model?.view);
752
+ const state = modals.state.value;
753
+ const getSchema = useCallback4(() => {
754
+ if (!space || state?.type !== "createRefPanel") {
755
+ return;
756
+ }
757
+ const staticSchema = client.graph.schemaRegistry.schemas.find((schema3) => Type.getTypename(schema3) === state.typename);
758
+ if (staticSchema) {
759
+ return staticSchema;
760
+ }
761
+ const [schema2] = space.db.schemaRegistry.query({
762
+ typename: state.typename
763
+ }).runSync();
764
+ return schema2;
765
+ }, [
766
+ space,
767
+ state
768
+ ]);
769
+ const schema = useMemo4(() => {
770
+ const schema2 = getSchema();
771
+ if (!schema2) {
772
+ return;
773
+ }
774
+ const omit = Schema.omit("id");
775
+ return omit(schema2);
776
+ }, [
777
+ getSchema
778
+ ]);
779
+ const handleSave = useCallback4((values) => {
780
+ if (!model || !space || state?.type !== "createRefPanel") {
781
+ return;
782
+ }
783
+ const schema2 = getSchema();
784
+ if (schema2) {
785
+ const obj = space.db.add(live(schema2, values));
786
+ state.onCreate?.(obj);
2092
787
  }
2093
- return extension2;
788
+ void modals.close();
2094
789
  }, [
2095
790
  model,
2096
791
  modals,
2097
- editing,
2098
- fieldProjection,
2099
- handleClose,
2100
- themeMode
792
+ space,
793
+ state
2101
794
  ]);
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) : "";
795
+ const handleCancel = useCallback4(() => {
796
+ if (model) {
797
+ modals.close();
2143
798
  }
2144
799
  }, [
2145
- model,
2146
- editing
800
+ modals
2147
801
  ]);
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
- }));
802
+ if (!model?.projection) {
803
+ return null;
804
+ }
805
+ return /* @__PURE__ */ React6.createElement(Popover3.Root, {
806
+ modal: false,
807
+ open: state?.type === "createRefPanel" && !!schema,
808
+ onOpenChange: (nextOpen) => {
809
+ if (model && !nextOpen) {
810
+ return modals.close();
811
+ }
812
+ }
813
+ }, /* @__PURE__ */ React6.createElement(Popover3.VirtualTrigger, {
814
+ virtualRef: modals.trigger
815
+ }), /* @__PURE__ */ React6.createElement(Popover3.Portal, null, /* @__PURE__ */ React6.createElement(Popover3.Content, {
816
+ classNames: "md:is-64",
817
+ "data-grid": gridId
818
+ }, /* @__PURE__ */ React6.createElement(Popover3.Viewport, null, state?.type === "createRefPanel" && schema && /* @__PURE__ */ React6.createElement(Form2, {
819
+ schema,
820
+ values: state.initialValues ?? {},
821
+ onSave: handleSave,
822
+ onCancel: handleCancel
823
+ })), /* @__PURE__ */ React6.createElement(Popover3.Arrow, null))));
2156
824
  } finally {
2157
825
  _effect.f();
2158
826
  }
2159
827
  };
2160
- var determineNavigationAxis = ({ key }) => {
2161
- switch (key) {
2162
- case "ArrowUp":
2163
- case "ArrowDown":
2164
- case "Enter": {
2165
- return "row";
828
+
829
+ // src/components/Table/RowActionsMenu.tsx
830
+ import { useSignals as _useSignals7 } from "@preact-signals/safe-react/tracking";
831
+ import React7 from "react";
832
+ import { DropdownMenu as DropdownMenu2, toLocalizedString, useTranslation as useTranslation2 } from "@dxos/react-ui";
833
+ var RowActionsMenu = ({ model, modals }) => {
834
+ var _effect = _useSignals7();
835
+ try {
836
+ const { t } = useTranslation2(translationKey);
837
+ const hasSelection = model.selection.hasSelection.value;
838
+ const state = modals.state.value;
839
+ if (state?.type !== "row") {
840
+ return null;
2166
841
  }
2167
- case "ArrowLeft":
2168
- case "ArrowRight":
2169
- case "Tab":
2170
- return "col";
842
+ return /* @__PURE__ */ React7.createElement(DropdownMenu2.Root, {
843
+ modal: false,
844
+ open: true,
845
+ onOpenChange: modals.close
846
+ }, /* @__PURE__ */ React7.createElement(DropdownMenu2.VirtualTrigger, {
847
+ virtualRef: modals.trigger
848
+ }), /* @__PURE__ */ React7.createElement(DropdownMenu2.Content, null, /* @__PURE__ */ React7.createElement(DropdownMenu2.Viewport, null, model.features.dataEditable !== false && /* @__PURE__ */ React7.createElement(DropdownMenu2.Item, {
849
+ "data-testid": "row-menu-delete",
850
+ onClick: () => model.deleteRow(state.rowIndex)
851
+ }, t(hasSelection ? "bulk delete row label" : "delete row label")), model.rowActions?.map((action) => /* @__PURE__ */ React7.createElement(DropdownMenu2.Item, {
852
+ key: action.id,
853
+ "data-testid": `row-action-${action.id}`,
854
+ onClick: () => {
855
+ modals.close();
856
+ model.handleRowAction(action.id, state.rowIndex);
857
+ }
858
+ }, toLocalizedString(action.label, t)))), /* @__PURE__ */ React7.createElement(DropdownMenu2.Arrow, null)));
859
+ } finally {
860
+ _effect.f();
2171
861
  }
2172
862
  };
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;
863
+
864
+ // src/components/Table/Table.tsx
865
+ var columnDefault = {
866
+ grid: {
867
+ minSize: 80,
868
+ maxSize: 640
2181
869
  }
2182
- return shift ? -1 : 1;
2183
870
  };
2184
-
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 }) => {
871
+ var rowDefault = {
872
+ frozenRowsStart: {
873
+ readonly: true,
874
+ focusUnfurl: false
875
+ }
876
+ };
877
+ var TableRoot = ({ children, role = "article" }) => {
2188
878
  var _effect = _useSignals8();
2189
879
  try {
2190
880
  return /* @__PURE__ */ React8.createElement("div", {
2191
881
  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]")
882
+ 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]", [
883
+ "card--popover",
884
+ "section",
885
+ "card--extrinsic"
886
+ ].includes(role) && "overflow-hidden", [
887
+ "article",
888
+ "slide"
889
+ ].includes(role) && "flex flex-col [&_.dx-grid]:grow [&_.dx-grid]:bs-0")
2193
890
  }, children);
2194
891
  } finally {
2195
892
  _effect.f();
@@ -2198,19 +895,27 @@ var TableRoot = ({ children, role }) => {
2198
895
  var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttention, schema, client, onRowClick }, forwardedRef) => {
2199
896
  var _effect = _useSignals8();
2200
897
  try {
2201
- const [dxGrid, setDxGrid] = useState3(null);
898
+ const [dxGrid, setDxGrid] = useState4(null);
2202
899
  const { hasAttention } = useAttention(model?.id ?? "table");
2203
- const { t } = useTranslation3(translationKey);
2204
900
  const modals = useMemo5(() => new ModalController(), []);
901
+ const draftRowCount = model?.getDraftRowCount() ?? 0;
902
+ const handleSave = useCallback5(() => {
903
+ dxGrid?.updateCells(true);
904
+ dxGrid?.requestUpdate();
905
+ }, [
906
+ dxGrid
907
+ ]);
2205
908
  const frozen = useMemo5(() => {
2206
909
  const noActionColumn = model?.features.dataEditable === false && model?.features.schemaEditable === false && model.rowActions.length === 0;
2207
910
  return {
2208
911
  frozenRowsStart: 1,
912
+ frozenRowsEnd: Math.max(1, draftRowCount),
2209
913
  frozenColsStart: model?.features.selection.enabled ? 1 : 0,
2210
914
  frozenColsEnd: noActionColumn ? 0 : 1
2211
915
  };
2212
916
  }, [
2213
- model
917
+ model,
918
+ draftRowCount
2214
919
  ]);
2215
920
  const getCells = useCallback5((range, plane) => presentation?.getCells(range, plane) ?? {}, [
2216
921
  presentation
@@ -2225,6 +930,30 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
2225
930
  dxGrid,
2226
931
  getCells
2227
932
  ]);
933
+ const handleInsertRowResult = useCallback5((insertResult) => {
934
+ if (insertResult === "draft") {
935
+ requestAnimationFrame(() => {
936
+ dxGrid?.setFocus({
937
+ plane: "frozenRowsEnd",
938
+ col: 0,
939
+ row: 0
940
+ });
941
+ dxGrid?.refocus();
942
+ });
943
+ } else {
944
+ requestAnimationFrame(() => {
945
+ dxGrid?.setFocus({
946
+ plane: "grid",
947
+ col: 0,
948
+ row: model ? model.getRowCount() - 1 : 0
949
+ });
950
+ dxGrid?.refocus();
951
+ });
952
+ }
953
+ }, [
954
+ model,
955
+ dxGrid
956
+ ]);
2228
957
  useImperativeHandle(forwardedRef, () => {
2229
958
  if (!presentation || !dxGrid) {
2230
959
  return {};
@@ -2232,23 +961,43 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
2232
961
  return {
2233
962
  update: (cell) => {
2234
963
  if (cell) {
2235
- dxGrid.updateIfWithinBounds(cell);
964
+ dxGrid.updateIfWithinBounds(cell, true);
2236
965
  } else {
2237
966
  dxGrid.updateCells(true);
2238
967
  dxGrid.requestUpdate();
2239
968
  }
2240
- }
969
+ },
970
+ handleInsertRowResult
2241
971
  };
2242
972
  }, [
2243
973
  presentation,
974
+ dxGrid,
975
+ model
976
+ ]);
977
+ const handleSaveDraftRow = useCallback5((rowIndex = 0) => {
978
+ if (model && dxGrid) {
979
+ const didCommitSuccessfully = model.commitDraftRow(rowIndex);
980
+ if (didCommitSuccessfully) {
981
+ requestAnimationFrame(() => {
982
+ dxGrid.scrollToEndRow();
983
+ });
984
+ }
985
+ }
986
+ }, [
987
+ model,
2244
988
  dxGrid
2245
989
  ]);
2246
990
  const handleGridClick = useCallback5((event) => {
2247
- const rowIndex = safeParseInt(event.target.ariaRowIndex ?? "");
2248
- if (rowIndex != null) {
991
+ const cell = closestCell(event.target);
992
+ if (cell) {
993
+ const { row: rowIndex, plane } = cell;
2249
994
  if (onRowClick) {
2250
- const row = model?.getRowAt(rowIndex);
2251
- row && onRowClick(row);
995
+ if (plane === "grid") {
996
+ const row = model?.getRowAt(rowIndex);
997
+ row && onRowClick(row);
998
+ } else {
999
+ onRowClick(cell);
1000
+ }
2252
1001
  }
2253
1002
  if (model?.features.selection.enabled && model?.selection.selectionMode === "single") {
2254
1003
  model.selection.toggleSelectionForRowIndex(rowIndex);
@@ -2283,6 +1032,10 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
2283
1032
  model?.sorting?.toggleSort(data.fieldId);
2284
1033
  break;
2285
1034
  }
1035
+ case "saveDraftRow": {
1036
+ handleSaveDraftRow();
1037
+ break;
1038
+ }
2286
1039
  }
2287
1040
  return;
2288
1041
  }
@@ -2316,31 +1069,23 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
2316
1069
  }
2317
1070
  }, [
2318
1071
  model,
2319
- modals
2320
- ]);
2321
- const handleFocus = useCallback5((increment, delta) => {
2322
- if (dxGrid) {
2323
- dxGrid.refocus(increment, delta);
2324
- }
2325
- }, [
1072
+ modals,
2326
1073
  dxGrid
2327
1074
  ]);
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);
2338
- });
1075
+ const handleFocus = useCallback5((increment, delta, cell) => {
1076
+ if (dxGrid && model) {
1077
+ if (cell?.plane === "grid" && cell?.row >= model.getRowCount() - 1) {
1078
+ handleInsertRowResult(draftRowCount < 1 ? model.insertRow() : "final");
1079
+ } else if (cell?.plane === "frozenRowsEnd" && increment === "row") {
1080
+ handleSaveDraftRow(cell.row);
1081
+ handleInsertRowResult(model.insertRow());
1082
+ } else {
1083
+ dxGrid.refocus(increment, delta);
2339
1084
  }
2340
1085
  }
2341
1086
  }, [
2342
- model,
2343
- dxGrid
1087
+ dxGrid,
1088
+ model
2344
1089
  ]);
2345
1090
  const handleKeyDown = useCallback5((event) => {
2346
1091
  if (!model?.features.dataEditable) {
@@ -2350,15 +1095,63 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
2350
1095
  if (!model || !cell) {
2351
1096
  return;
2352
1097
  }
1098
+ if (event.metaKey || event.ctrlKey) {
1099
+ switch (event.key) {
1100
+ case "c": {
1101
+ try {
1102
+ const cellData = model.getCellData(cell);
1103
+ const textContent = cellData?.toString() ?? "";
1104
+ void navigator.clipboard.writeText(textContent);
1105
+ event.preventDefault();
1106
+ } catch (error) {
1107
+ console.warn("Failed to copy cell content:", error);
1108
+ }
1109
+ break;
1110
+ }
1111
+ case "v": {
1112
+ event.preventDefault();
1113
+ void navigator.clipboard.readText().then((clipboardText) => {
1114
+ try {
1115
+ model.setCellData(cell, clipboardText);
1116
+ handleSave();
1117
+ } catch {
1118
+ const cellElement = event.target.closest('[data-dx-grid-action="cell"]');
1119
+ if (cellElement) {
1120
+ const rect = cellElement.getBoundingClientRect();
1121
+ const editRequest = new DxEditRequest({
1122
+ cellIndex: `${cell.plane},${cell.col},${cell.row}`,
1123
+ cellBox: {
1124
+ insetInlineStart: rect.left,
1125
+ insetBlockStart: rect.top,
1126
+ inlineSize: rect.width,
1127
+ blockSize: rect.height
1128
+ },
1129
+ cellElement,
1130
+ initialContent: clipboardText
1131
+ });
1132
+ cellElement.dispatchEvent(editRequest);
1133
+ }
1134
+ }
1135
+ });
1136
+ break;
1137
+ }
1138
+ }
1139
+ }
2353
1140
  switch (event.key) {
2354
1141
  case "Backspace":
2355
1142
  case "Delete": {
2356
- model.setCellData(cell, void 0);
1143
+ try {
1144
+ model.setCellData(cell, void 0);
1145
+ event.preventDefault();
1146
+ } catch {
1147
+ }
2357
1148
  break;
2358
1149
  }
2359
1150
  }
2360
1151
  }, [
2361
- model
1152
+ model,
1153
+ dxGrid,
1154
+ handleSave
2362
1155
  ]);
2363
1156
  const handleAxisResize = useCallback5((event) => {
2364
1157
  if (event.axis === "col") {
@@ -2385,54 +1178,6 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
2385
1178
  model,
2386
1179
  dxGrid
2387
1180
  ]);
2388
- const handleQuery = useCallback5(async ({ field, props }, text) => {
2389
- if (model && props.referenceSchema && field.referencePath) {
2390
- const space = model.space;
2391
- invariant4(space, void 0, {
2392
- F: __dxlog_file5,
2393
- L: 311,
2394
- S: void 0,
2395
- A: [
2396
- "space",
2397
- ""
2398
- ]
2399
- });
2400
- let schema2;
2401
- if (client) {
2402
- schema2 = client.graph.schemaRegistry.getSchema(props.referenceSchema);
2403
- }
2404
- if (!schema2) {
2405
- schema2 = space.db.schemaRegistry.getSchema(props.referenceSchema);
2406
- }
2407
- if (schema2) {
2408
- const { objects } = await space.db.query(Filter.type(schema2)).run();
2409
- const options = objects.map((obj) => {
2410
- const value = getValue4(obj, field.referencePath);
2411
- if (!value || typeof value !== "string") {
2412
- return void 0;
2413
- }
2414
- return {
2415
- label: value,
2416
- data: obj
2417
- };
2418
- }).filter(isNotFalsy);
2419
- return [
2420
- ...options,
2421
- {
2422
- label: t("create new object label", {
2423
- text
2424
- }),
2425
- data: createOption(text)
2426
- }
2427
- ];
2428
- }
2429
- }
2430
- return [];
2431
- }, [
2432
- model,
2433
- client,
2434
- t
2435
- ]);
2436
1181
  if (!model || !modals) {
2437
1182
  return /* @__PURE__ */ React8.createElement("span", {
2438
1183
  role: "none",
@@ -2445,20 +1190,21 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
2445
1190
  model,
2446
1191
  modals,
2447
1192
  schema,
2448
- onEnter: handleEnter,
2449
1193
  onFocus: handleFocus,
2450
- onQuery: handleQuery
1194
+ onSave: handleSave,
1195
+ client
2451
1196
  }), /* @__PURE__ */ React8.createElement(Grid.Content, {
2452
- className: mx2("[--dx-grid-base:var(--baseSurface)]", gridSeparatorInlineEnd, gridSeparatorBlockEnd),
1197
+ className: mx("[--dx-grid-base:var(--baseSurface)]", gridSeparatorInlineEnd, gridSeparatorBlockEnd),
2453
1198
  frozen,
2454
- // getCells={getCells}
2455
1199
  columns: model.columnMeta.value,
1200
+ columnDefault,
1201
+ rowDefault,
2456
1202
  limitRows: model.getRowCount() ?? 0,
2457
- limitColumns: model.view?.fields?.length ?? 0,
1203
+ limitColumns: model.projection.fields.length,
2458
1204
  overscroll: "trap",
2459
1205
  onAxisResize: handleAxisResize,
2460
1206
  onClick: handleGridClick,
2461
- onKeyDown: handleKeyDown,
1207
+ onKeyDownCapture: handleKeyDown,
2462
1208
  onWheelCapture: handleWheel,
2463
1209
  ref: setDxGrid
2464
1210
  }), /* @__PURE__ */ React8.createElement(RowActionsMenu, {
@@ -2484,116 +1230,33 @@ var Table = {
2484
1230
  Main: TableMain
2485
1231
  };
2486
1232
 
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
1233
+ // src/components/Table/DynamicTable.tsx
2494
1234
  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 }) => {
1235
+ import React9, { useCallback as useCallback6, useMemo as useMemo6, useRef as useRef3 } from "react";
1236
+ import { useDefaultValue } from "@dxos/react-ui";
1237
+ import { mx as mx2 } from "@dxos/react-ui-theme";
1238
+ var DynamicTable = ({ classNames, name = "example.com/dynamic-table", rows, properties, jsonSchema: _jsonSchema, schema, rowActions, onRowClick, onRowAction, ...props }) => {
2502
1239
  var _effect = _useSignals9();
2503
1240
  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
- }, [
1241
+ const { typename, jsonSchema } = useMemo6(() => getBaseSchema({
1242
+ typename: name,
1243
+ properties,
1244
+ jsonSchema: _jsonSchema,
1245
+ schema
1246
+ }), [
2593
1247
  name,
2594
1248
  properties,
2595
- schema,
2596
- jsonSchema
1249
+ _jsonSchema,
1250
+ schema
1251
+ ]);
1252
+ const { projection, view } = useMemo6(() => makeDynamicTable({
1253
+ typename,
1254
+ jsonSchema,
1255
+ properties
1256
+ }), [
1257
+ typename,
1258
+ jsonSchema,
1259
+ properties
2597
1260
  ]);
2598
1261
  const tableRef = useRef3(null);
2599
1262
  const handleCellUpdate = useCallback6((cell) => {
@@ -2609,8 +1272,9 @@ var DynamicTable = ({ classNames, name = "example.com/dynamic-table", rows, prop
2609
1272
  dataEditable: false
2610
1273
  }));
2611
1274
  const model = useTableModel({
2612
- table,
2613
1275
  rows,
1276
+ view,
1277
+ schema: jsonSchema,
2614
1278
  projection,
2615
1279
  features,
2616
1280
  rowActions,
@@ -2627,7 +1291,7 @@ var DynamicTable = ({ classNames, name = "example.com/dynamic-table", rows, prop
2627
1291
  ]);
2628
1292
  return /* @__PURE__ */ React9.createElement("div", {
2629
1293
  role: "none",
2630
- className: mx3("is-full bs-full grow grid", classNames)
1294
+ className: mx2("is-full bs-full grow grid", classNames)
2631
1295
  }, /* @__PURE__ */ React9.createElement("div", {
2632
1296
  role: "none",
2633
1297
  className: "grid min-bs-0 overflow-hidden"
@@ -2643,12 +1307,12 @@ var DynamicTable = ({ classNames, name = "example.com/dynamic-table", rows, prop
2643
1307
  }
2644
1308
  };
2645
1309
 
2646
- // packages/ui/react-ui-table/src/components/TableToolbar/TableToolbar.tsx
2647
- import { useSignals as _useSignals11 } from "@preact-signals/safe-react/tracking";
1310
+ // src/components/TableToolbar/TableToolbar.tsx
1311
+ import { useSignals as _useSignals10 } from "@preact-signals/safe-react/tracking";
2648
1312
  import { Rx } from "@effect-rx/rx-react";
2649
1313
  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";
1314
+ import { live as live2 } from "@dxos/live-object";
1315
+ import { MenuProvider, ToolbarMenu, createGapSeparator, createMenuAction, useMenuActions } from "@dxos/react-ui-menu";
2652
1316
  import { rxFromSignal } from "@dxos/react-ui-menu";
2653
1317
  var createTableToolbarActions = ({ state, onAdd, onSave, customActions }) => Rx.make((get) => {
2654
1318
  const add = createMenuAction("add-row", onAdd, {
@@ -2696,9 +1360,9 @@ var createTableToolbarActions = ({ state, onAdd, onSave, customActions }) => Rx.
2696
1360
  };
2697
1361
  });
2698
1362
  var TableToolbar = ({ classNames, viewDirty, attendableId, onAdd, onSave, customActions }) => {
2699
- var _effect = _useSignals11();
1363
+ var _effect = _useSignals10();
2700
1364
  try {
2701
- const state = useMemo7(() => live3({
1365
+ const state = useMemo7(() => live2({
2702
1366
  viewDirty
2703
1367
  }), []);
2704
1368
  const actionsCreator = useMemo7(() => createTableToolbarActions({
@@ -2733,18 +1397,16 @@ export {
2733
1397
  Table,
2734
1398
  TableModel,
2735
1399
  TablePresentation,
2736
- TableSchema,
2737
1400
  TableToolbar,
2738
- TableType,
2739
1401
  cellClassesForRowSelection,
2740
1402
  getBaseSchema,
2741
- initializeTable,
2742
1403
  makeDynamicTable,
2743
1404
  narrowSchema,
2744
1405
  tableButtons,
2745
1406
  tableControls,
2746
1407
  touch,
2747
- translations_default as translations,
1408
+ translations,
1409
+ useAddRow,
2748
1410
  useTableModel
2749
1411
  };
2750
1412
  //# sourceMappingURL=index.mjs.map