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

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