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

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