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