@dxos/react-ui-table 0.8.4-main.ae835ea → 0.8.4-main.bc2380dfbc

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 (167) hide show
  1. package/LICENSE +102 -5
  2. package/dist/lib/browser/chunk-4URYOFDL.mjs +69 -0
  3. package/dist/lib/browser/chunk-4URYOFDL.mjs.map +7 -0
  4. package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
  5. package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +7 -0
  6. package/dist/lib/browser/{chunk-G7MGPBSR.mjs → chunk-YDXPC7HA.mjs} +642 -723
  7. package/dist/lib/browser/chunk-YDXPC7HA.mjs.map +7 -0
  8. package/dist/lib/browser/index.mjs +1136 -1044
  9. package/dist/lib/browser/index.mjs.map +4 -4
  10. package/dist/lib/browser/meta.json +1 -1
  11. package/dist/lib/browser/testing/index.mjs +35 -45
  12. package/dist/lib/browser/testing/index.mjs.map +3 -3
  13. package/dist/lib/browser/translations.mjs +36 -0
  14. package/dist/lib/browser/translations.mjs.map +7 -0
  15. package/dist/lib/browser/types/index.mjs +4 -3
  16. package/dist/lib/node-esm/{chunk-LD5JEBA6.mjs → chunk-2AN3QQ3D.mjs} +642 -722
  17. package/dist/lib/node-esm/chunk-2AN3QQ3D.mjs.map +7 -0
  18. package/dist/lib/node-esm/chunk-B2SZYAEC.mjs +70 -0
  19. package/dist/lib/node-esm/chunk-B2SZYAEC.mjs.map +7 -0
  20. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
  21. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +7 -0
  22. package/dist/lib/node-esm/index.mjs +1136 -1044
  23. package/dist/lib/node-esm/index.mjs.map +4 -4
  24. package/dist/lib/node-esm/meta.json +1 -1
  25. package/dist/lib/node-esm/testing/index.mjs +35 -45
  26. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  27. package/dist/lib/node-esm/translations.mjs +37 -0
  28. package/dist/lib/node-esm/translations.mjs.map +7 -0
  29. package/dist/lib/node-esm/types/index.mjs +4 -3
  30. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -2
  31. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  32. package/dist/types/src/components/Table/ColumnSettings.d.ts +2 -2
  33. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  34. package/dist/types/src/components/Table/DynamicTable.d.ts +6 -7
  35. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  36. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +21 -37
  37. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  38. package/dist/types/src/components/Table/Relations.stories.d.ts +84 -81
  39. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
  40. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  41. package/dist/types/src/components/Table/Table.d.ts +20 -21
  42. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  43. package/dist/types/src/components/Table/Table.stories.d.ts +92 -26
  44. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  45. package/dist/types/src/components/Table/TableContent.d.ts +17 -0
  46. package/dist/types/src/components/Table/TableContent.d.ts.map +1 -0
  47. package/dist/types/src/components/Table/TableToolbar.d.ts +21 -0
  48. package/dist/types/src/components/Table/TableToolbar.d.ts.map +1 -0
  49. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +1 -1
  50. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
  51. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +10 -12
  52. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  53. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +5 -7
  54. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  55. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +24 -26
  56. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  57. package/dist/types/src/components/index.d.ts +0 -1
  58. package/dist/types/src/components/index.d.ts.map +1 -1
  59. package/dist/types/src/components/testing.d.ts +22 -15
  60. package/dist/types/src/components/testing.d.ts.map +1 -1
  61. package/dist/types/src/hooks/index.d.ts +2 -1
  62. package/dist/types/src/hooks/index.d.ts.map +1 -1
  63. package/dist/types/src/hooks/useAddRow.d.ts +5 -6
  64. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  65. package/dist/types/src/hooks/useProjectionModel.d.ts +6 -0
  66. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
  67. package/dist/types/src/hooks/useTableModel.d.ts +9 -9
  68. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  69. package/dist/types/src/index.d.ts +0 -2
  70. package/dist/types/src/index.d.ts.map +1 -1
  71. package/dist/types/src/model/modal-controller.d.ts +5 -1
  72. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  73. package/dist/types/src/model/selection-model.d.ts +18 -9
  74. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  75. package/dist/types/src/model/table-model.d.ts +104 -25
  76. package/dist/types/src/model/table-model.d.ts.map +1 -1
  77. package/dist/types/src/model/table-presentation.d.ts +3 -1
  78. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  79. package/dist/types/src/playwright/TableManager.d.ts +2 -1
  80. package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
  81. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -1
  82. package/dist/types/src/testing/useTestTableModel.d.ts +18 -15
  83. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  84. package/dist/types/src/translations.d.ts +21 -23
  85. package/dist/types/src/translations.d.ts.map +1 -1
  86. package/dist/types/src/types/Table.d.ts +25 -0
  87. package/dist/types/src/types/Table.d.ts.map +1 -0
  88. package/dist/types/src/types/index.d.ts +1 -1
  89. package/dist/types/src/types/index.d.ts.map +1 -1
  90. package/dist/types/src/util/dynamic-table.d.ts +14 -11
  91. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  92. package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
  93. package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
  94. package/dist/types/src/util/index.d.ts +2 -0
  95. package/dist/types/src/util/index.d.ts.map +1 -1
  96. package/dist/types/src/util/query-ast.d.ts +7 -0
  97. package/dist/types/src/util/query-ast.d.ts.map +1 -0
  98. package/dist/types/src/util/schema.d.ts.map +1 -1
  99. package/dist/types/src/util/signals.d.ts.map +1 -1
  100. package/dist/types/src/util/sort.d.ts +12 -0
  101. package/dist/types/src/util/sort.d.ts.map +1 -0
  102. package/dist/types/src/util/table-buttons.d.ts +20 -20
  103. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  104. package/dist/types/src/util/table-controls.d.ts +4 -4
  105. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  106. package/dist/types/src/util/tag.d.ts.map +1 -1
  107. package/dist/types/tsconfig.tsbuildinfo +1 -1
  108. package/package.json +67 -76
  109. package/src/components/Table/ColumnActionsMenu.tsx +17 -12
  110. package/src/components/Table/ColumnSettings.tsx +8 -9
  111. package/src/components/Table/DynamicTable.stories.tsx +30 -30
  112. package/src/components/Table/DynamicTable.tsx +31 -32
  113. package/src/components/Table/Relations.stories.tsx +107 -68
  114. package/src/components/Table/RowActionsMenu.tsx +27 -17
  115. package/src/components/Table/Table.stories.tsx +123 -108
  116. package/src/components/Table/Table.tsx +43 -407
  117. package/src/components/Table/TableContent.tsx +421 -0
  118. package/src/components/Table/TableToolbar.tsx +111 -0
  119. package/src/components/TableCellEditor/CellValidationMessage.tsx +5 -6
  120. package/src/components/TableCellEditor/FormCellEditor.tsx +119 -135
  121. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +26 -22
  122. package/src/components/TableCellEditor/TableCellEditor.tsx +35 -33
  123. package/src/components/index.ts +0 -1
  124. package/src/components/testing.ts +43 -17
  125. package/src/hooks/index.ts +2 -1
  126. package/src/hooks/useAddRow.ts +8 -10
  127. package/src/hooks/useProjectionModel.ts +39 -0
  128. package/src/hooks/useTableModel.ts +36 -34
  129. package/src/index.ts +0 -4
  130. package/src/model/modal-controller.ts +23 -12
  131. package/src/model/selection-model.ts +75 -44
  132. package/src/model/table-model.test.ts +82 -55
  133. package/src/model/table-model.ts +435 -170
  134. package/src/model/table-presentation.test.ts +59 -50
  135. package/src/model/table-presentation.ts +43 -39
  136. package/src/playwright/TableManager.ts +13 -6
  137. package/src/playwright/playwright.config.ts +1 -1
  138. package/src/playwright/smoke.spec.ts +56 -10
  139. package/src/testing/useTestTableModel.ts +49 -42
  140. package/src/translations.ts +20 -22
  141. package/src/types/Table.ts +78 -0
  142. package/src/types/index.ts +1 -1
  143. package/src/util/dynamic-table.test.ts +70 -0
  144. package/src/util/dynamic-table.ts +40 -28
  145. package/src/util/index.ts +2 -0
  146. package/src/util/query-ast.ts +22 -0
  147. package/src/util/sort.ts +90 -0
  148. package/src/util/table-buttons.ts +1 -1
  149. package/src/util/table-controls.ts +2 -2
  150. package/dist/lib/browser/chunk-4K7JPALT.mjs +0 -66
  151. package/dist/lib/browser/chunk-4K7JPALT.mjs.map +0 -7
  152. package/dist/lib/browser/chunk-G7MGPBSR.mjs.map +0 -7
  153. package/dist/lib/node-esm/chunk-LD5JEBA6.mjs.map +0 -7
  154. package/dist/lib/node-esm/chunk-MK6OVUF6.mjs +0 -67
  155. package/dist/lib/node-esm/chunk-MK6OVUF6.mjs.map +0 -7
  156. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +0 -21
  157. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +0 -1
  158. package/dist/types/src/components/TableToolbar/index.d.ts +0 -2
  159. package/dist/types/src/components/TableToolbar/index.d.ts.map +0 -1
  160. package/dist/types/src/model/table-sorting.d.ts +0 -52
  161. package/dist/types/src/model/table-sorting.d.ts.map +0 -1
  162. package/dist/types/src/types/schema.d.ts +0 -28
  163. package/dist/types/src/types/schema.d.ts.map +0 -1
  164. package/src/components/TableToolbar/TableToolbar.tsx +0 -105
  165. package/src/components/TableToolbar/index.ts +0 -5
  166. package/src/model/table-sorting.ts +0 -225
  167. package/src/types/schema.ts +0 -73
@@ -1,86 +1,65 @@
1
- import {
2
- schema_exports
3
- } from "./chunk-4K7JPALT.mjs";
4
-
5
- // src/util/dynamic-table.ts
6
- import { Filter, Obj, Query } from "@dxos/echo";
7
- import { getTypename, toJsonSchema } from "@dxos/echo/internal";
8
- import { ProjectionModel, createView, getSchemaFromPropertyDefinitions } from "@dxos/schema";
9
- var getBaseSchema = ({ typename, properties, jsonSchema, schema }) => {
10
- if (typename && properties) {
11
- const schema2 = getSchemaFromPropertyDefinitions(typename, properties);
12
- return {
13
- typename: schema2.typename,
14
- jsonSchema: schema2.jsonSchema
15
- };
16
- } else if (schema) {
17
- return {
18
- typename: getTypename(schema),
19
- jsonSchema: toJsonSchema(schema)
20
- };
21
- } else if (typename && jsonSchema) {
22
- return {
23
- typename,
24
- jsonSchema
25
- };
26
- } else {
27
- throw new Error("invalid properties");
1
+ // src/util/query-ast.ts
2
+ var extractOrder = (queryAst) => {
3
+ if (queryAst.type === "order") {
4
+ return queryAst.order;
28
5
  }
6
+ if (queryAst.type === "options") {
7
+ return extractOrder(queryAst.query);
8
+ }
9
+ if (queryAst.type === "from") {
10
+ return extractOrder(queryAst.query);
11
+ }
12
+ return void 0;
29
13
  };
30
- var makeDynamicTable = ({ jsonSchema, properties }) => {
31
- const table = Obj.make(schema_exports.Table, {
32
- sizes: {}
33
- });
34
- const view = createView({
35
- query: Query.select(Filter.everything()),
36
- jsonSchema,
37
- presentation: table,
38
- ...properties && {
39
- fields: properties.map((property) => property.name)
40
- }
41
- });
42
- const projection = new ProjectionModel(jsonSchema, view.projection);
43
- projection.normalizeView();
44
- if (properties && projection.fields) {
45
- setProperties(view, projection, table, properties);
46
- }
47
- return {
48
- projection,
49
- view
50
- };
51
- };
52
- var setProperties = (view, projection, table, properties) => {
53
- for (const property of properties) {
54
- const field = projection.fields.find((field2) => field2.path === property.name);
55
- if (field) {
56
- if (property.size !== void 0) {
57
- table.sizes[field.path] = property.size;
58
- }
59
- if (property.title !== void 0) {
60
- const fieldProjection = projection.getFieldProjection(field.id);
61
- projection.setFieldProjection({
62
- ...fieldProjection,
63
- props: {
64
- ...fieldProjection.props,
65
- title: property.title
66
- }
67
- });
68
- }
69
- if (property.sort) {
70
- const fieldId = field.id;
71
- view.sort = [
72
- {
73
- fieldId,
74
- direction: property.sort
75
- }
76
- ];
77
- }
14
+
15
+ // src/util/sort.ts
16
+ import { Format } from "@dxos/echo";
17
+ var getSortValue = (value, format) => {
18
+ if (value === void 0 || value === null) {
19
+ return null;
20
+ }
21
+ const isDateFormat = format === Format.TypeFormat.DateTime || format === Format.TypeFormat.Date;
22
+ if (isDateFormat) {
23
+ const date = new Date(value);
24
+ if (!isNaN(date.getTime())) {
25
+ return date.getTime();
78
26
  }
27
+ return String(value);
79
28
  }
29
+ if (format === Format.TypeFormat.Number || format === Format.TypeFormat.Integer || format === Format.TypeFormat.Currency || format === Format.TypeFormat.Percent || format === Format.TypeFormat.Duration) {
30
+ const num = typeof value === "number" ? value : Number(value);
31
+ if (!isNaN(num)) {
32
+ return num;
33
+ }
34
+ return String(value);
35
+ }
36
+ if (typeof value === "string") {
37
+ return value;
38
+ }
39
+ if (typeof value === "number") {
40
+ return value;
41
+ }
42
+ return String(value);
43
+ };
44
+ var compareValues = (aValue, bValue, format, direction) => {
45
+ if (aValue === void 0 || aValue === null) {
46
+ return bValue === void 0 || bValue === null ? 0 : 1;
47
+ }
48
+ if (bValue === void 0 || bValue === null) {
49
+ return -1;
50
+ }
51
+ const aSortValue = getSortValue(aValue, format);
52
+ const bSortValue = getSortValue(bValue, format);
53
+ let comparison = 0;
54
+ if (typeof aSortValue === "number" && typeof bSortValue === "number") {
55
+ comparison = aSortValue - bSortValue;
56
+ } else if (typeof aSortValue === "string" && typeof bSortValue === "string") {
57
+ comparison = aSortValue.localeCompare(bSortValue);
58
+ } else {
59
+ comparison = String(aSortValue).localeCompare(String(bSortValue));
60
+ }
61
+ return direction === "desc" ? -comparison : comparison;
80
62
  };
81
-
82
- // src/util/signals.ts
83
- var touch = (value) => value;
84
63
 
85
64
  // src/util/table-buttons.ts
86
65
  var BUTTON_IDENTIFIERS = {
@@ -93,7 +72,7 @@ var BUTTON_IDENTIFIERS = {
93
72
  };
94
73
  var createButton = ({ attr, data, disabled = false, icon, testId, type: _type = "primary" }) => {
95
74
  const dataAttrs = Object.entries(data).map(([k, v]) => `${k}="${v}"`).join(" ");
96
- return `<button ${attr} data-testid="${testId}" class="dx-button is-6 aspect-square pli-0.5 min-bs-0" ${dataAttrs} ${disabled ? "disabled" : ""} data-dx-grid-action="accessory"><svg data-size="4"><use href="/icons.svg#${icon}"/></svg></button>`;
75
+ return `<button ${attr} data-testid="${testId}" class="dx-button w-6 aspect-square px-0.5 min-h-0" ${dataAttrs} ${disabled ? "disabled" : ""} data-dx-grid-action="accessory"><svg data-size="4"><use href="/icons.svg#${icon}"/></svg></button>`;
97
76
  };
98
77
  var addColumnButton = {
99
78
  attr: BUTTON_IDENTIFIERS.newColumn,
@@ -206,8 +185,8 @@ var CONTROL_IDENTIFIERS = {
206
185
  switch: "data-table-switch"
207
186
  };
208
187
  var BASE_CLASSES = {
209
- checkbox: "absolute inset-block-[.375rem] inline-end-[.375rem] dx-checkbox",
210
- switch: "absolute inset-block-[.25rem] inline-end-[.25rem] dx-checkbox--switch"
188
+ checkbox: "absolute inset-y-[.375rem] end-[.375rem] dx-checkbox",
189
+ switch: "absolute inset-y-[.25rem] end-[.25rem] dx-checkbox--switch"
211
190
  };
212
191
  var renderAttributes = (data) => {
213
192
  return Object.entries(data).map(([k, v]) => `${k}="${v}"`).join(" ");
@@ -255,32 +234,17 @@ var tableControls = {
255
234
  }
256
235
  };
257
236
 
258
- // src/util/schema.ts
259
- import * as Schema from "effect/Schema";
260
- import * as SchemaAST from "effect/SchemaAST";
261
- var narrowSchema = (schema, paths) => {
262
- const ast = schema?.ast;
263
- if (SchemaAST.isTypeLiteral(ast)) {
264
- const propertySignatures = ast.propertySignatures.filter((signature) => paths.includes(signature.name.toString()));
265
- if (propertySignatures.length > 0) {
266
- const narrowType = new SchemaAST.TypeLiteral(propertySignatures, []);
267
- return Schema.make(narrowType);
268
- }
269
- }
270
- return void 0;
271
- };
272
-
273
237
  // src/model/table-model.ts
274
- import { computed as computed3, effect as effect2, signal as signal3 } from "@preact/signals-core";
238
+ import { Atom as Atom2 } from "@effect-atom/atom-react";
275
239
  import { Resource as Resource2 } from "@dxos/context";
276
- import { Obj as Obj2, Ref } from "@dxos/echo";
277
- import { FormatEnum as FormatEnum2, getSchema, getValue as getValue2, setValue, toEffectSchema } from "@dxos/echo/internal";
240
+ import { Format as Format2, Obj, Order, Query, Ref } from "@dxos/echo";
241
+ import { toEffectSchema } from "@dxos/echo/internal";
242
+ import { getSnapshot } from "@dxos/echo/internal";
243
+ import { getValue, setValue } from "@dxos/effect";
278
244
  import { invariant } from "@dxos/invariant";
279
245
  import { ObjectId } from "@dxos/keys";
280
- import { getSnapshot, isLiveObject } from "@dxos/live-object";
281
- import { fullyQualifiedId } from "@dxos/react-client/echo";
282
246
  import { formatForEditing, parseValue } from "@dxos/react-ui-form";
283
- import { ProjectionModel as ProjectionModel2, validateSchema } from "@dxos/schema";
247
+ import { validateSchema } from "@dxos/schema";
284
248
 
285
249
  // src/util/tag.ts
286
250
  var TAG_PATTERN_REGEX = /\[([^\]]+)\]\(([^)]+)\)/g;
@@ -299,75 +263,99 @@ var extractTagIds = (value) => {
299
263
  };
300
264
 
301
265
  // src/model/selection-model.ts
302
- import { computed, effect, signal } from "@preact/signals-core";
266
+ import { Atom } from "@effect-atom/atom-react";
303
267
  import { Resource } from "@dxos/context";
304
268
  var SelectionModel = class extends Resource {
269
+ _registry;
305
270
  _rows;
271
+ _selection;
272
+ _validSelectedIds;
273
+ _hasSelection;
274
+ _allSelected;
306
275
  _selectionMode;
307
- _initialSelection;
308
276
  _onSelectionChanged;
309
- _selection;
310
- _validSelectedIds = computed(() => {
311
- const rows = this._rows.value;
312
- if (!rows) {
313
- return /* @__PURE__ */ new Set();
314
- }
315
- const validIds = new Set(rows.map((row) => row.id));
316
- return new Set([
317
- ...this._selection.value
318
- ].filter((id) => validIds.has(id)));
319
- });
320
- _hasSelection = computed(() => this._validSelectedIds.value.size > 0);
321
- _allSelected = computed(() => {
322
- const rows = this._rows.value;
323
- if (rows.length === 0) {
324
- return false;
325
- }
326
- return rows.every((row) => this._selection.value.has(row.id));
327
- });
328
- constructor(_rows, _selectionMode, _initialSelection, _onSelectionChanged) {
329
- super(), this._rows = _rows, this._selectionMode = _selectionMode, this._initialSelection = _initialSelection, this._onSelectionChanged = _onSelectionChanged;
330
- this._selection = signal(new Set(this._initialSelection));
277
+ constructor(registry, rows, selectionMode, initialSelection, onSelectionChanged) {
278
+ super();
279
+ this._registry = registry;
280
+ this._rows = rows;
281
+ this._selectionMode = selectionMode;
282
+ this._onSelectionChanged = onSelectionChanged;
283
+ this._selection = Atom.make(new Set(initialSelection));
284
+ this._validSelectedIds = Atom.make((get) => {
285
+ const rows2 = get(this._rows);
286
+ if (!rows2) {
287
+ return /* @__PURE__ */ new Set();
288
+ }
289
+ const validIds = new Set(rows2.map((row) => row.id));
290
+ const selection = get(this._selection);
291
+ return new Set([
292
+ ...selection
293
+ ].filter((id) => validIds.has(id)));
294
+ });
295
+ this._hasSelection = Atom.make((get) => get(this._validSelectedIds).size > 0);
296
+ this._allSelected = Atom.make((get) => {
297
+ const rows2 = get(this._rows);
298
+ if (rows2.length === 0) {
299
+ return false;
300
+ }
301
+ const selection = get(this._selection);
302
+ return rows2.every((row) => selection.has(row.id));
303
+ });
331
304
  }
332
305
  async _open() {
333
- const selectionWatcher = effect(() => {
334
- touch(this._selection.value);
306
+ const selectionUnsubscribe = this._registry.subscribe(this._selection, () => {
335
307
  this._onSelectionChanged?.();
336
308
  });
337
- this._ctx.onDispose(selectionWatcher);
309
+ this._ctx.onDispose(selectionUnsubscribe);
338
310
  }
339
311
  //
340
312
  // Getters
341
313
  //
342
- get selection() {
314
+ /** Get the selection atom for reactive access. */
315
+ get selectionAtom() {
343
316
  return this._selection;
344
317
  }
345
- get hasSelection() {
318
+ /** Get the current selection value. */
319
+ get selection() {
320
+ return this._registry.get(this._selection);
321
+ }
322
+ /** Get the hasSelection atom for reactive access. */
323
+ get hasSelectionAtom() {
346
324
  return this._hasSelection;
347
325
  }
348
- get allRowsSeleted() {
326
+ /** Check if there is any selection. */
327
+ get hasSelection() {
328
+ return this._registry.get(this._hasSelection);
329
+ }
330
+ /** Get the allRowsSelected atom for reactive access. */
331
+ get allRowsSelectedAtom() {
349
332
  return this._allSelected;
350
333
  }
334
+ /** Check if all rows are selected. */
335
+ get allRowsSelected() {
336
+ return this._registry.get(this._allSelected);
337
+ }
351
338
  get selectionMode() {
352
339
  return this._selectionMode;
353
340
  }
354
341
  getSelectedRows = () => {
355
- const selectedIds = this._validSelectedIds.value;
356
- return this._rows.value.filter((row) => selectedIds.has(row.id));
342
+ const selectedIds = this._registry.get(this._validSelectedIds);
343
+ return this._registry.get(this._rows).filter((row) => selectedIds.has(row.id));
357
344
  };
358
345
  isRowIndexSelected = (rowIndex) => {
359
- const row = this._rows.value[rowIndex];
360
- return this._selection.value.has(row.id);
346
+ const row = this._registry.get(this._rows)[rowIndex];
347
+ return this._registry.get(this._selection).has(row.id);
361
348
  };
362
349
  isObjectSelected = (object) => {
363
- return this._selection.value.has(object.id);
350
+ return this._registry.get(this._selection).has(object.id);
364
351
  };
365
352
  //
366
353
  // Manipulation
367
354
  //
368
355
  toggleSelectionForRowIndex = (rowIndex) => {
369
- const row = this._rows.value[rowIndex];
370
- const newSelection = new Set(this._selection.value);
356
+ const row = this._registry.get(this._rows)[rowIndex];
357
+ const currentSelection = this._registry.get(this._selection);
358
+ const newSelection = new Set(currentSelection);
371
359
  if (newSelection.has(row.id)) {
372
360
  newSelection.delete(row.id);
373
361
  } else {
@@ -376,194 +364,38 @@ var SelectionModel = class extends Resource {
376
364
  }
377
365
  newSelection.add(row.id);
378
366
  }
379
- this._selection.value = newSelection;
367
+ this._registry.set(this._selection, newSelection);
380
368
  };
381
369
  setSelection = (mode) => {
382
370
  switch (mode) {
383
371
  case "all": {
384
- this._selection.value = new Set(this._rows.value.map((row) => row.id));
372
+ this._registry.set(this._selection, new Set(this._registry.get(this._rows).map((row) => row.id)));
385
373
  break;
386
374
  }
387
375
  case "none": {
388
- this._selection.value = /* @__PURE__ */ new Set();
376
+ this._registry.set(this._selection, /* @__PURE__ */ new Set());
389
377
  break;
390
378
  }
391
379
  }
392
380
  };
393
381
  };
394
382
 
395
- // src/model/table-sorting.ts
396
- import { computed as computed2, signal as signal2 } from "@preact/signals-core";
397
- import orderBy from "lodash.orderby";
398
- import { FormatEnum, TypeEnum, getValue } from "@dxos/echo/internal";
399
- import { formatForDisplay } from "@dxos/react-ui-form";
400
- var TableSorting = class {
401
- _view;
402
- _projection;
403
- _displayToDataIndex = /* @__PURE__ */ new Map();
404
- _rows;
405
- _localSort = signal2(void 0);
406
- _isDirty;
407
- sortedRows;
408
- constructor(rows, _view, _projection) {
409
- this._view = _view;
410
- this._projection = _projection;
411
- this._rows = rows;
412
- this._isDirty = computed2(() => {
413
- const local = this._localSort.value;
414
- const viewSort = this._view.sort?.[0];
415
- if (local?.type === "cleared") {
416
- return viewSort !== void 0;
417
- }
418
- if (local?.type === "active") {
419
- if (!viewSort) {
420
- return true;
421
- }
422
- return local.sort.fieldId !== viewSort.fieldId || local.sort.direction !== viewSort.direction;
423
- }
424
- return false;
425
- });
426
- this.sortedRows = this.initialiseSortedRows();
427
- }
428
- /**
429
- * @reactive
430
- * @returns Local sort if present, falls back to view sort.
431
- */
432
- get sorting() {
433
- const local = this._localSort.value;
434
- if (local?.type === "cleared") {
435
- return void 0;
436
- }
437
- if (local?.type === "active") {
438
- return local.sort;
439
- }
440
- return this._view.sort?.[0];
441
- }
442
- /**
443
- * @reactive
444
- * @returns Whether local sort differs from view sort.
445
- */
446
- get isDirty() {
447
- return this._isDirty.value;
448
- }
449
- getDataIndex(displayIndex) {
450
- return this._displayToDataIndex.get(displayIndex) ?? displayIndex;
451
- }
452
- setSort(fieldId, direction) {
453
- this._localSort.value = {
454
- type: "active",
455
- sort: {
456
- fieldId,
457
- direction
458
- }
459
- };
460
- }
461
- toggleSort(fieldId) {
462
- if (!this.sorting || this.sorting.fieldId !== fieldId) {
463
- return;
464
- }
465
- switch (this.sorting.direction) {
466
- case "asc": {
467
- this.setSort(fieldId, "desc");
468
- break;
469
- }
470
- case "desc": {
471
- this.setSort(fieldId, "asc");
472
- break;
473
- }
474
- }
475
- }
476
- clearSort() {
477
- this._localSort.value = {
478
- type: "cleared"
479
- };
480
- }
481
- save() {
482
- if (this._localSort.value !== void 0) {
483
- if (this._localSort.value.type === "active") {
484
- this._view.sort = [
485
- this._localSort.value.sort
486
- ];
487
- } else {
488
- this._view.sort = [];
489
- }
490
- this._localSort.value = void 0;
491
- }
492
- }
493
- //
494
- // Initialisation.
495
- //
496
- initialiseSortedRows() {
497
- return computed2(() => {
498
- this._displayToDataIndex.clear();
499
- const sort = this.sorting;
500
- if (!sort) {
501
- return this._rows.value;
502
- }
503
- const field = this._projection.fields.find((f) => f.id === sort.fieldId);
504
- if (!field) {
505
- return this._rows.value;
506
- }
507
- const { props } = this._projection.getFieldProjection(field.id);
508
- const dataWithIndices = this._rows.value.map((item, index) => {
509
- const sortValue = this.getSortValue(props, field, item);
510
- const isEmpty = !(props.type === TypeEnum.Boolean) && (sortValue == null || sortValue === "");
511
- return {
512
- item,
513
- index,
514
- value: sortValue,
515
- isEmpty
516
- };
517
- });
518
- const ordered = orderBy(dataWithIndices, [
519
- "isEmpty",
520
- "value"
521
- ], [
522
- "asc",
523
- sort.direction
524
- ]);
525
- ordered.forEach(({ index: dataIndex }, displayIndex) => {
526
- if (displayIndex !== dataIndex) {
527
- this._displayToDataIndex.set(displayIndex, dataIndex);
528
- }
529
- });
530
- return ordered.map(({ item }) => item);
531
- });
532
- }
533
- getSortValue(props, field, item) {
534
- const { type, format } = props;
535
- const value = getValue(item, field.path);
536
- if (type === TypeEnum.Boolean) {
537
- return !!value;
538
- }
539
- if (type === TypeEnum.Number) {
540
- return value;
541
- }
542
- if (value == null) {
543
- return value;
544
- }
545
- if (format === FormatEnum.SingleSelect) {
546
- const options = props.options;
547
- if (!options) {
548
- return void 0;
549
- }
550
- const option = options.find((option2) => option2.id === value);
551
- return option ? options.indexOf(option) : void 0;
552
- }
553
- if (format === FormatEnum.Ref && field.referencePath) {
554
- const refValue = getValue(value.target, field.referencePath);
555
- return refValue;
556
- }
557
- return formatForDisplay({
558
- type,
559
- format,
560
- value
561
- });
562
- }
563
- };
564
-
565
383
  // src/model/table-model.ts
566
384
  var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-table/src/model/table-model.ts";
385
+ var createEchoChangeCallback = (table) => ({
386
+ table: (mutate) => Obj.update(table, (table2) => mutate(table2)),
387
+ row: (row, mutate) => {
388
+ if (Obj.isObject(row)) {
389
+ Obj.update(row, (row2) => mutate(row2));
390
+ } else {
391
+ mutate(row);
392
+ }
393
+ }
394
+ });
395
+ var createDirectChangeCallback = (table) => ({
396
+ table: (mutate) => mutate(table),
397
+ row: (row, mutate) => mutate(row)
398
+ });
567
399
  var defaultFeatures = {
568
400
  selection: {
569
401
  enabled: true,
@@ -573,133 +405,201 @@ var defaultFeatures = {
573
405
  schemaEditable: false
574
406
  };
575
407
  var TableModel = class extends Resource2 {
576
- _view;
408
+ _registry;
409
+ _object;
577
410
  _projection;
578
- _table;
579
- _visibleRange = signal3({
580
- start: {
581
- row: 0,
582
- col: 0
583
- },
584
- end: {
585
- row: 0,
586
- col: 0
587
- }
588
- });
411
+ _db;
412
+ _change;
413
+ _visibleRange;
589
414
  _onInsertRow;
590
415
  _onDeleteRows;
591
- _onDeleteColumn;
416
+ _onColumnDelete;
592
417
  _onCellUpdate;
593
418
  _onRowOrderChange;
594
419
  _onRowAction;
595
420
  _rowActions;
596
421
  _features;
597
- _rows = signal3([]);
598
- _draftRows = signal3([]);
422
+ _rows;
423
+ _draftRows;
424
+ // In-memory sort state - changes are local until saved.
425
+ _inMemorySort;
426
+ // Derived atom for persisted sort from view.query.ast.
427
+ _persistedSort;
428
+ // Derived atom for current sort (in-memory takes precedence over persisted).
599
429
  _sorting;
430
+ // Derived atom for sorted rows (used by rows getter and SelectionModel).
431
+ _sortedRows;
432
+ // Derived atom for viewDirty state.
433
+ _viewDirty;
600
434
  _pinnedRows;
601
435
  _selection;
602
436
  _columnMeta;
603
- constructor({ view, schema, projection, features = {}, sorting = [], initialSelection = [], pinnedRows = {
437
+ // Counter atom that increments when cells are updated - allows UI to react to cell changes.
438
+ _cellUpdateCounter;
439
+ constructor({ registry, object, projection, db, change, features = {}, initialSelection = [], pinnedRows = {
604
440
  top: [],
605
441
  bottom: []
606
- }, rowActions = [], onCellUpdate, onDeleteColumn, onDeleteRows, onInsertRow, onRowOrderChange, onRowAction }) {
442
+ }, rowActions = [], onCellUpdate, onColumnDelete, onDeleteRows, onInsertRow, onRowOrderChange, onRowAction }) {
607
443
  super();
608
- this._view = view;
609
- this._projection = projection ?? new ProjectionModel2(schema, view.projection);
444
+ this._registry = registry;
445
+ this._object = object;
446
+ this._projection = projection;
610
447
  this._projection.normalizeView();
448
+ this._db = db;
449
+ this._change = change ?? createEchoChangeCallback(object);
611
450
  this._features = {
612
451
  ...defaultFeatures,
613
452
  ...features
614
453
  };
615
- invariant(!(this._features.dataEditable && this._features.selection.enabled && this._features.selection.mode === "single"), "Single selection is not compatible with editable tables.", {
616
- F: __dxlog_file,
617
- L: 149,
618
- S: this,
619
- A: [
620
- "!(this._features.dataEditable && this._features.selection.enabled && this._features.selection.mode === 'single')",
621
- "'Single selection is not compatible with editable tables.'"
622
- ]
454
+ invariant(!(this._features.dataEditable && this._features.selection.enabled && this._features.selection.mode === "single"), "Single selection is not compatible with editable tables.", { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 94, S: this, A: ["!(this._features.dataEditable && this._features.selection.enabled && this._features.selection.mode === 'single')", "'Single selection is not compatible with editable tables.'"] });
455
+ this._visibleRange = Atom2.make({
456
+ start: {
457
+ row: 0,
458
+ col: 0
459
+ },
460
+ end: {
461
+ row: 0,
462
+ col: 0
463
+ }
464
+ });
465
+ this._rows = Atom2.make([]);
466
+ this._draftRows = Atom2.make([]);
467
+ this._inMemorySort = Atom2.make(void 0);
468
+ this._cellUpdateCounter = Atom2.make(0);
469
+ this._persistedSort = Atom2.make((_get) => {
470
+ const viewSnapshot = Obj.getSnapshot(this.view);
471
+ const ast = viewSnapshot.query.ast;
472
+ const orders = extractOrder(ast);
473
+ if (orders && orders.length > 0) {
474
+ const firstOrder = orders[0];
475
+ if (firstOrder.kind === "property") {
476
+ const field = this._projection.getFields().find((f) => f.path === firstOrder.property);
477
+ if (field) {
478
+ return {
479
+ fieldId: field.id,
480
+ direction: firstOrder.direction
481
+ };
482
+ }
483
+ }
484
+ }
485
+ return void 0;
486
+ });
487
+ this._sorting = Atom2.make((get) => {
488
+ const inMemorySort = get(this._inMemorySort);
489
+ return inMemorySort ?? get(this._persistedSort);
490
+ });
491
+ this._sortedRows = Atom2.make((get) => {
492
+ const rows = get(this._rows);
493
+ const sort = get(this._sorting);
494
+ if (!sort || rows.length === 0) {
495
+ return rows;
496
+ }
497
+ const field = this._projection.getFields().find((f) => f.id === sort.fieldId);
498
+ if (!field) {
499
+ return rows;
500
+ }
501
+ const fieldProjection = this._projection.getFieldProjection(field.id);
502
+ const sorted = [
503
+ ...rows
504
+ ].sort((a, b) => {
505
+ const aValue = getValue(a, field.path);
506
+ const bValue = getValue(b, field.path);
507
+ return compareValues(aValue, bValue, fieldProjection.props.format, sort.direction);
508
+ });
509
+ return sorted;
510
+ });
511
+ this._selection = new SelectionModel(this._registry, this._sortedRows, this._features.selection.mode ?? "multiple", initialSelection, () => this._onRowOrderChange?.());
512
+ this._viewDirty = Atom2.make((get) => {
513
+ const inMemorySort = get(this._inMemorySort);
514
+ if (!inMemorySort) {
515
+ return false;
516
+ }
517
+ const persisted = get(this._persistedSort);
518
+ if (!persisted) {
519
+ return true;
520
+ }
521
+ return inMemorySort.fieldId !== persisted.fieldId || inMemorySort.direction !== persisted.direction;
623
522
  });
624
- this._sorting = new TableSorting(this._rows, this._view, this._projection);
625
- if (sorting.length > 0) {
626
- const [sort] = sorting;
627
- this._sorting.setSort(sort.fieldId, sort.direction);
628
- }
629
- this._selection = new SelectionModel(this._sorting.sortedRows, this._features.selection.mode ?? "multiple", initialSelection, () => this._onRowOrderChange?.());
630
523
  this._pinnedRows = pinnedRows;
631
524
  this._rowActions = rowActions;
632
525
  this._onInsertRow = onInsertRow;
633
526
  this._onDeleteRows = onDeleteRows;
634
- this._onDeleteColumn = onDeleteColumn;
527
+ this._onColumnDelete = onColumnDelete;
635
528
  this._onCellUpdate = onCellUpdate;
636
529
  this._onRowOrderChange = onRowOrderChange;
637
530
  this._onRowAction = onRowAction;
638
531
  }
639
532
  get id() {
640
- return fullyQualifiedId(this._view);
533
+ return Obj.getDXN(this._object).toString();
641
534
  }
642
535
  get view() {
643
- return this._view;
536
+ const view = this._object.view.target;
537
+ invariant(view, "Table model not initialized", { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 186, S: this, A: ["view", "'Table model not initialized'"] });
538
+ return view;
644
539
  }
645
540
  get table() {
646
- invariant(this._table, "Model not initialized", {
647
- F: __dxlog_file,
648
- L: 187,
649
- S: this,
650
- A: [
651
- "this._table",
652
- "'Model not initialized'"
653
- ]
654
- });
655
- return this._table;
541
+ return this._object;
656
542
  }
657
543
  get projection() {
658
544
  return this._projection;
659
545
  }
546
+ get db() {
547
+ return this._db;
548
+ }
549
+ /**
550
+ * Atom for reactive access to rows with in-memory sorting applied.
551
+ */
660
552
  get rows() {
661
- return this.sorting.sortedRows;
553
+ return this._sortedRows;
554
+ }
555
+ /**
556
+ * Gets rows with in-memory sorting applied.
557
+ * In-memory sort is local to this instance until saved.
558
+ */
559
+ getRows() {
560
+ return this._registry.get(this._sortedRows);
561
+ }
562
+ /** Atom for reactive access to cell changes. */
563
+ get cellUpdate() {
564
+ return this._cellUpdateCounter;
565
+ }
566
+ /**
567
+ * Change a row using the configured change callback.
568
+ * Use this instead of directly mutating to ensure consistent mutation handling.
569
+ */
570
+ changeRow(row, mutate) {
571
+ this._change.row(row, mutate);
662
572
  }
663
573
  get features() {
664
574
  return this._features;
665
575
  }
666
576
  /**
667
- * @reactive
577
+ * Gets the current sort state.
578
+ * Priority: in-memory sort (local changes) > persisted sort (from query AST).
668
579
  */
669
- get sorting() {
670
- return this._sorting;
580
+ getSorting() {
581
+ return this._registry.get(this._sorting);
671
582
  }
672
583
  /**
673
584
  * Gets the row data at the specified display index.
674
585
  */
675
586
  getRowAt(displayIndex) {
676
- if (displayIndex < 0 || displayIndex >= this.sorting.sortedRows.value.length) {
587
+ const sortedRows = this._registry.get(this._sortedRows);
588
+ if (displayIndex < 0 || displayIndex >= sortedRows.length) {
677
589
  return void 0;
678
590
  }
679
- return this.sorting.sortedRows.value[displayIndex];
591
+ return sortedRows[displayIndex];
680
592
  }
681
593
  get pinnedRows() {
682
594
  return this._pinnedRows;
683
595
  }
684
596
  get columnMeta() {
685
- invariant(this._columnMeta, void 0, {
686
- F: __dxlog_file,
687
- L: 226,
688
- S: this,
689
- A: [
690
- "this._columnMeta",
691
- ""
692
- ]
693
- });
597
+ invariant(this._columnMeta, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 240, S: this, A: ["this._columnMeta", ""] });
694
598
  return this._columnMeta;
695
599
  }
696
600
  get selection() {
697
601
  return this._selection;
698
602
  }
699
- /** @reactive */
700
- get isViewDirty() {
701
- return this.sorting.isDirty;
702
- }
703
603
  get rowActions() {
704
604
  return this._rowActions;
705
605
  }
@@ -707,24 +607,15 @@ var TableModel = class extends Resource2 {
707
607
  // Initialisation
708
608
  //
709
609
  async _open() {
710
- const presentation = this._view.presentation.target ?? await this._view.presentation.load();
711
- invariant(Obj2.instanceOf(schema_exports.Table, presentation), void 0, {
712
- F: __dxlog_file,
713
- L: 249,
714
- S: this,
715
- A: [
716
- "Obj.instanceOf(Table.Table, presentation)",
717
- ""
718
- ]
719
- });
720
- this._table = presentation;
610
+ await this._object.view.load();
721
611
  this.initializeColumnMeta();
722
612
  this.initializeEffects();
723
613
  await this._selection.open(this._ctx);
724
614
  }
725
615
  initializeColumnMeta() {
726
- this._columnMeta = computed3(() => {
727
- const fields = this._projection?.fields ?? [];
616
+ invariant(this._object.view.target, "View must be loaded before initializing column meta", { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 259, S: this, A: ["this._object.view.target", "'View must be loaded before initializing column meta'"] });
617
+ this._columnMeta = Atom2.make((get) => {
618
+ const fields = get(this._projection.fields);
728
619
  const meta = Object.fromEntries(fields.map((field, index) => [
729
620
  index,
730
621
  {
@@ -750,49 +641,62 @@ var TableModel = class extends Resource2 {
750
641
  });
751
642
  }
752
643
  initializeEffects() {
753
- const rowOrderWatcher = effect2(() => {
754
- touch(this.sorting.sortedRows.value);
644
+ const rowsUnsubscribe = this._registry.subscribe(this._rows, () => {
755
645
  this._onRowOrderChange?.();
756
646
  });
757
- this._ctx.onDispose(rowOrderWatcher);
758
- const rowEffectManager = effect2(() => {
759
- const { start, end } = touch(this._visibleRange.value);
760
- const rowEffects = [];
761
- for (let row = start.row; row <= end.row; row++) {
762
- rowEffects.push(effect2(() => {
763
- const obj = this.sorting.sortedRows.value[row];
764
- this._projection?.fields.forEach((field) => touch(getValue2(obj, field.path)));
765
- this._onCellUpdate?.({
766
- row,
767
- col: start.col,
768
- plane: "grid"
647
+ this._ctx.onDispose(rowsUnsubscribe);
648
+ const rowSubscriptions = /* @__PURE__ */ new Map();
649
+ const visibleRangeUnsubscribe = this._registry.subscribe(this._visibleRange, () => {
650
+ for (const [id, unsub] of rowSubscriptions) {
651
+ unsub();
652
+ rowSubscriptions.delete(id);
653
+ }
654
+ const { start, end } = this._registry.get(this._visibleRange);
655
+ const sortedRows = this._registry.get(this._sortedRows);
656
+ for (let row = start.row; row <= end.row && row < sortedRows.length; row++) {
657
+ const obj = sortedRows[row];
658
+ if (Obj.isObject(obj) && !rowSubscriptions.has(obj.id)) {
659
+ const unsub = Obj.subscribe(obj, () => {
660
+ this._registry.set(this._cellUpdateCounter, this._registry.get(this._cellUpdateCounter) + 1);
661
+ this._onCellUpdate?.({
662
+ row,
663
+ col: start.col,
664
+ plane: "grid"
665
+ });
769
666
  });
770
- }));
667
+ rowSubscriptions.set(obj.id, unsub);
668
+ }
771
669
  }
772
- return () => rowEffects.forEach((cleanup) => cleanup());
773
670
  });
774
- this._ctx.onDispose(rowEffectManager);
775
- const draftRowsWatcher = effect2(() => {
776
- touch(this._draftRows.value);
671
+ this._ctx.onDispose(() => {
672
+ visibleRangeUnsubscribe();
673
+ for (const unsub of rowSubscriptions.values()) {
674
+ unsub();
675
+ }
676
+ });
677
+ const draftRowsUnsubscribe = this._registry.subscribe(this._draftRows, () => {
777
678
  this._onRowOrderChange?.();
778
679
  });
779
- this._ctx.onDispose(draftRowsWatcher);
680
+ this._ctx.onDispose(draftRowsUnsubscribe);
780
681
  }
781
682
  //
782
683
  // Data
783
684
  //
784
685
  setRows = (obj) => {
785
- this._rows.value = obj;
686
+ this._registry.set(this._rows, obj);
786
687
  };
787
- getRowCount = () => this._rows.value.length;
788
- /**
789
- * @reactive
790
- */
791
- getDraftRowCount = () => this._draftRows.value.length;
792
- // TODO(ZaymonFC): Return .value instead of exposing the signal.
688
+ getRowCount = () => this._registry.get(this._sortedRows).length;
689
+ getDraftRowCount() {
690
+ return this._registry.get(this._draftRows).length;
691
+ }
692
+ /** Atom for reactive access to draft rows. */
793
693
  get draftRows() {
794
694
  return this._draftRows;
795
695
  }
696
+ /** Gets the current draft rows. */
697
+ getDraftRows() {
698
+ return this._registry.get(this._draftRows);
699
+ }
796
700
  /**
797
701
  * Checks if a specific field path has validation errors for a given draft row.
798
702
  * @param draftRowIndex - The index of the draft row to check
@@ -800,17 +704,17 @@ var TableModel = class extends Resource2 {
800
704
  * @returns true if the field has validation errors, false otherwise
801
705
  */
802
706
  hasDraftRowValidationError(draftRowIndex, fieldPath) {
803
- if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.value.length) {
707
+ if (draftRowIndex < 0 || draftRowIndex >= this._registry.get(this._draftRows).length) {
804
708
  return false;
805
709
  }
806
- const draftRow = this._draftRows.value[draftRowIndex];
710
+ const draftRow = this._registry.get(this._draftRows)[draftRowIndex];
807
711
  const validationErrors = draftRow.validationErrors || [];
808
712
  return validationErrors.some((error) => error.path === fieldPath);
809
713
  }
810
- getColumnCount = () => this._projection?.fields.length ?? 0;
714
+ getColumnCount = () => this._projection?.getFields().length ?? 0;
811
715
  insertRow = () => {
812
716
  const result = this._onInsertRow?.();
813
- if (result === "draft" && this._draftRows.value.length === 0) {
717
+ if (result === "draft" && this._registry.get(this._draftRows).length === 0) {
814
718
  this.createDraftRow();
815
719
  }
816
720
  return result ?? "final";
@@ -828,16 +732,16 @@ var TableModel = class extends Resource2 {
828
732
  valid: validationErrors.length === 0,
829
733
  validationErrors
830
734
  };
831
- this._draftRows.value = [
832
- ...this._draftRows.value,
735
+ this._registry.set(this._draftRows, [
736
+ ...this._registry.get(this._draftRows),
833
737
  draftRow
834
- ];
738
+ ]);
835
739
  }
836
740
  validateDraftRow(draftRowIndex) {
837
- if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.value.length) {
741
+ if (draftRowIndex < 0 || draftRowIndex >= this._registry.get(this._draftRows).length) {
838
742
  return;
839
743
  }
840
- const draftRow = this._draftRows.value[draftRowIndex];
744
+ const draftRow = this._registry.get(this._draftRows)[draftRowIndex];
841
745
  const validationErrors = this.validateDraftRowData(draftRow.data);
842
746
  const updatedDraftRow = {
843
747
  ...draftRow,
@@ -845,80 +749,82 @@ var TableModel = class extends Resource2 {
845
749
  validationErrors
846
750
  };
847
751
  const newDraftRows = [
848
- ...this._draftRows.value
752
+ ...this._registry.get(this._draftRows)
849
753
  ];
850
754
  newDraftRows[draftRowIndex] = updatedDraftRow;
851
- this._draftRows.value = newDraftRows;
755
+ this._registry.set(this._draftRows, newDraftRows);
852
756
  }
853
757
  validateDraftRowData(data) {
854
758
  const schema = toEffectSchema(this._projection.baseSchema);
855
759
  return validateSchema(schema, data) || [];
856
760
  }
857
761
  commitDraftRow = (draftRowIndex) => {
858
- if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.value.length) {
762
+ if (draftRowIndex < 0 || draftRowIndex >= this._registry.get(this._draftRows).length) {
859
763
  return false;
860
764
  }
861
- const draftRow = this._draftRows.value[draftRowIndex];
765
+ const draftRow = this._registry.get(this._draftRows)[draftRowIndex];
862
766
  if (!draftRow.valid) {
863
767
  return false;
864
768
  }
865
769
  const insertRowResult = this._onInsertRow?.(draftRow.data);
866
770
  if (insertRowResult === "final") {
867
- const newDraftRows = this._draftRows.value.filter((_, index) => index !== draftRowIndex);
868
- this._draftRows.value = newDraftRows;
771
+ const newDraftRows = this._registry.get(this._draftRows).filter((_, index) => index !== draftRowIndex);
772
+ this._registry.set(this._draftRows, newDraftRows);
869
773
  }
870
774
  return insertRowResult === "final";
871
775
  };
872
776
  deleteRow = (rowIndex) => {
873
- const row = this._sorting.getDataIndex(rowIndex);
874
- const obj = this._rows.value[row];
777
+ const sortedRows = this._registry.get(this._sortedRows);
778
+ const obj = sortedRows[rowIndex];
875
779
  const objectsToDelete = [];
876
- if (this.features.selection.enabled && this._selection.hasSelection.value) {
780
+ if (this.features.selection.enabled && this._selection.hasSelection) {
877
781
  const selectedRows = this._selection.getSelectedRows();
878
782
  objectsToDelete.push(...selectedRows);
879
- } else {
783
+ } else if (obj) {
880
784
  objectsToDelete.push(obj);
881
785
  }
882
- this._onDeleteRows?.(row, objectsToDelete);
786
+ if (objectsToDelete.length > 0) {
787
+ this._onDeleteRows?.(rowIndex, objectsToDelete);
788
+ }
883
789
  };
884
790
  handleRowAction = (actionId, rowIndex) => {
885
791
  if (!this._onRowAction) {
886
792
  return;
887
793
  }
888
- const row = this._sorting.getDataIndex(rowIndex);
889
- const data = this._rows.value[row];
794
+ const sortedRows = this._registry.get(this._sortedRows);
795
+ const data = sortedRows[rowIndex];
890
796
  if (data) {
891
797
  this._onRowAction(actionId, data);
892
798
  }
893
799
  };
894
800
  getCellData = (cell) => {
895
801
  const { col, row, plane } = cell;
896
- const fields = this._projection?.fields ?? [];
802
+ const fields = this._projection?.getFields() ?? [];
897
803
  if (col < 0 || col >= fields.length) {
898
804
  return void 0;
899
805
  }
900
806
  const field = fields[col];
901
807
  let value;
902
808
  if (plane === "frozenRowsEnd") {
903
- if (row >= 0 && row < this._draftRows.value.length) {
904
- value = getValue2(this._draftRows.value[row].data, field.path);
809
+ if (row >= 0 && row < this._registry.get(this._draftRows).length) {
810
+ value = getValue(this._registry.get(this._draftRows)[row].data, field.path);
905
811
  } else {
906
812
  return void 0;
907
813
  }
908
814
  } else {
909
- const dataIndex = this._sorting.getDataIndex(row);
910
- value = getValue2(this._rows.value[dataIndex], field.path);
815
+ const sortedRows = this._registry.get(this._sortedRows);
816
+ value = getValue(sortedRows[row], field.path);
911
817
  }
912
818
  if (value == null) {
913
819
  return "";
914
820
  }
915
821
  const { props } = this._projection.getFieldProjection(field.id);
916
822
  switch (props.format) {
917
- case FormatEnum2.Ref: {
823
+ case Format2.TypeFormat.Ref: {
918
824
  if (!field.referencePath) {
919
825
  return "";
920
826
  }
921
- return getValue2(value.target, field.referencePath);
827
+ return getValue(value.target, field.referencePath);
922
828
  }
923
829
  default: {
924
830
  return formatForEditing({
@@ -930,7 +836,7 @@ var TableModel = class extends Resource2 {
930
836
  }
931
837
  };
932
838
  validateCellData = async ({ col, row, plane }, value) => {
933
- const fields = this._projection?.fields ?? [];
839
+ const fields = this._projection?.getFields() ?? [];
934
840
  if (col < 0 || col >= fields.length) {
935
841
  return {
936
842
  valid: false,
@@ -942,14 +848,14 @@ var TableModel = class extends Resource2 {
942
848
  const transformedValue = editorTextToCellValue(props, value);
943
849
  const isDraftRow = plane === "frozenRowsEnd";
944
850
  if (isDraftRow) {
945
- const isInvalidDraftRowIndex = row < 0 || row >= this._draftRows.value.length;
851
+ const isInvalidDraftRowIndex = row < 0 || row >= this._registry.get(this._draftRows).length;
946
852
  if (isInvalidDraftRowIndex) {
947
853
  return {
948
854
  valid: false,
949
855
  error: "Invalid draft row index"
950
856
  };
951
857
  }
952
- const draftRow = this._draftRows.value[row];
858
+ const draftRow = this._registry.get(this._draftRows)[row];
953
859
  const snapshot = {
954
860
  ...draftRow.data
955
861
  };
@@ -968,41 +874,18 @@ var TableModel = class extends Resource2 {
968
874
  valid: true
969
875
  };
970
876
  } else {
971
- const rowIdx = this._sorting.getDataIndex(row);
972
- const currentRow = this._rows.value[rowIdx];
973
- invariant(currentRow, "Invalid row index", {
974
- F: __dxlog_file,
975
- L: 522,
976
- S: this,
977
- A: [
978
- "currentRow",
979
- "'Invalid row index'"
980
- ]
981
- });
982
- const snapshot = getSnapshot(currentRow);
877
+ const currentRow = this._registry.get(this._rows)[row];
878
+ invariant(currentRow, "Invalid row index", { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 533, S: this, A: ["currentRow", "'Invalid row index'"] });
879
+ const snapshot = {
880
+ ...getSnapshot(currentRow)
881
+ };
983
882
  setValue(snapshot, field.path, transformedValue);
984
- const schema = getSchema(currentRow);
985
- invariant(schema, void 0, {
986
- F: __dxlog_file,
987
- L: 528,
988
- S: this,
989
- A: [
990
- "schema",
991
- ""
992
- ]
993
- });
883
+ const schema = Obj.getSchema(currentRow);
884
+ invariant(schema, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 539, S: this, A: ["schema", ""] });
994
885
  const validationResult = validateSchema(schema, snapshot);
995
886
  if (validationResult && validationResult.length > 0) {
996
887
  const error = validationResult[0];
997
- invariant(error.path === field.path, void 0, {
998
- F: __dxlog_file,
999
- L: 533,
1000
- S: this,
1001
- A: [
1002
- "error.path === field.path",
1003
- ""
1004
- ]
1005
- });
888
+ invariant(error.path === field.path, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 543, S: this, A: ["error.path === field.path", ""] });
1006
889
  return {
1007
890
  valid: false,
1008
891
  error: error.message
@@ -1015,24 +898,27 @@ var TableModel = class extends Resource2 {
1015
898
  };
1016
899
  setCellData = (cell, value) => {
1017
900
  const { col, row, plane } = cell;
1018
- const fields = this._projection?.fields ?? [];
901
+ const fields = this._projection?.getFields() ?? [];
1019
902
  if (col < 0 || col >= fields.length) {
1020
903
  return;
1021
904
  }
1022
905
  const field = fields[col];
1023
906
  const { props } = this._projection.getFieldProjection(field.id);
1024
907
  const transformedValue = editorTextToCellValue(props, value);
1025
- if (props.format === FormatEnum2.Ref && !isLiveObject(value)) {
908
+ if (props.format === Format2.TypeFormat.Ref && !Obj.isObject(value)) {
1026
909
  return;
1027
910
  }
1028
911
  if (plane === "frozenRowsEnd") {
1029
- if (row >= 0 && row < this._draftRows.value.length) {
1030
- setValue(this._draftRows.value[row].data, field.path, transformedValue);
912
+ if (row >= 0 && row < this._registry.get(this._draftRows).length) {
913
+ setValue(this._registry.get(this._draftRows)[row].data, field.path, transformedValue);
1031
914
  this.validateDraftRow(row);
1032
915
  }
1033
916
  } else {
1034
- const rowIdx = this._sorting.getDataIndex(row);
1035
- setValue(this._rows.value[rowIdx], field.path, transformedValue);
917
+ const sortedRows = this._registry.get(this._sortedRows);
918
+ const rowData = sortedRows[row];
919
+ this._change.row(rowData, (mutableRow) => {
920
+ setValue(mutableRow, field.path, transformedValue);
921
+ });
1036
922
  }
1037
923
  };
1038
924
  /**
@@ -1041,12 +927,15 @@ var TableModel = class extends Resource2 {
1041
927
  * @param {(value: any) => any} update - A function that takes the current value and returns the updated value.
1042
928
  */
1043
929
  updateCellData({ col, row }, update) {
1044
- const dataRow = this._sorting.getDataIndex(row);
1045
- const fields = this._projection?.fields ?? [];
930
+ const fields = this._projection?.getFields() ?? [];
1046
931
  const field = fields[col];
1047
- const value = getValue2(this._rows.value[dataRow], field.path);
932
+ const sortedRows = this._registry.get(this._sortedRows);
933
+ const rowData = sortedRows[row];
934
+ const value = getValue(rowData, field.path);
1048
935
  const updatedValue = update(value);
1049
- setValue(this._rows.value[dataRow], field.path, updatedValue);
936
+ this._change.row(rowData, (mutableRow) => {
937
+ setValue(mutableRow, field.path, updatedValue);
938
+ });
1050
939
  }
1051
940
  /**
1052
941
  * Gets the domain-appropriate cell type for a given cell position.
@@ -1080,21 +969,23 @@ var TableModel = class extends Resource2 {
1080
969
  if (!this._projection) {
1081
970
  return;
1082
971
  }
1083
- const field = this._projection?.fields.find((field2) => field2.id === fieldId);
1084
- if (field && this._onDeleteColumn) {
1085
- this._onDeleteColumn(field.id);
972
+ const field = this._projection?.getFields().find((field2) => field2.id === fieldId);
973
+ if (field && this._onColumnDelete) {
974
+ this._onColumnDelete(field.id);
1086
975
  }
1087
976
  }
1088
977
  //
1089
978
  // Resize
1090
979
  //
1091
980
  setColumnWidth(columnIndex, width) {
1092
- const fields = this._projection?.fields ?? [];
981
+ const fields = this._projection?.getFields() ?? [];
1093
982
  if (columnIndex < fields.length) {
1094
983
  const newWidth = Math.max(0, width);
1095
984
  const field = fields[columnIndex];
1096
985
  if (field) {
1097
- this.table.sizes[field.path] = newWidth;
986
+ this._change.table((mutableTable) => {
987
+ mutableTable.sizes[field.path] = newWidth;
988
+ });
1098
989
  }
1099
990
  }
1100
991
  }
@@ -1110,22 +1001,92 @@ var TableModel = class extends Resource2 {
1110
1001
  this._pinnedRows.bottom = this._pinnedRows.bottom.filter((index) => index !== rowIndex);
1111
1002
  }
1112
1003
  //
1113
- // View operations
1004
+ // Sort operations
1114
1005
  //
1006
+ /**
1007
+ * Sets the sort order for a field in-memory (local to this instance).
1008
+ * Use saveView() to persist the sort to the view query.
1009
+ */
1010
+ setSort(fieldId, direction) {
1011
+ const field = this._projection.getFields().find((f) => f.id === fieldId);
1012
+ if (!field) {
1013
+ return;
1014
+ }
1015
+ this._registry.set(this._inMemorySort, {
1016
+ fieldId,
1017
+ direction
1018
+ });
1019
+ }
1020
+ /**
1021
+ * Toggles the sort direction for a field in-memory.
1022
+ */
1023
+ toggleSort(fieldId) {
1024
+ const currentSort = this.getSorting();
1025
+ if (!currentSort || currentSort.fieldId !== fieldId) {
1026
+ this.setSort(fieldId, "asc");
1027
+ return;
1028
+ }
1029
+ const newDirection = currentSort.direction === "asc" ? "desc" : "asc";
1030
+ this.setSort(fieldId, newDirection);
1031
+ }
1032
+ /**
1033
+ * Clears the in-memory sort order.
1034
+ */
1035
+ clearSort() {
1036
+ this._registry.set(this._inMemorySort, void 0);
1037
+ }
1038
+ /**
1039
+ * Saves the current in-memory sort to the view query AST.
1040
+ * This persists the sort so it becomes the default for all viewers.
1041
+ */
1115
1042
  saveView() {
1116
- this._sorting.save();
1043
+ const view = this.view;
1044
+ const inMemorySort = this._registry.get(this._inMemorySort);
1045
+ const viewSnapshot = Obj.getSnapshot(view);
1046
+ const currentQuery = Query.fromAst(viewSnapshot.query.ast);
1047
+ const baseQuery = this._getBaseQuery(currentQuery);
1048
+ if (inMemorySort) {
1049
+ const field = this._projection.getFields().find((f) => f.id === inMemorySort.fieldId);
1050
+ if (field) {
1051
+ const newQuery = baseQuery.orderBy(Order.property(field.path, inMemorySort.direction));
1052
+ Obj.update(view, (view2) => {
1053
+ view2.query.ast = newQuery.ast;
1054
+ });
1055
+ }
1056
+ } else {
1057
+ Obj.update(view, (view2) => {
1058
+ view2.query.ast = baseQuery.ast;
1059
+ });
1060
+ }
1061
+ this._registry.set(this._inMemorySort, void 0);
1062
+ }
1063
+ /**
1064
+ * Extracts the base query without order clauses.
1065
+ */
1066
+ _getBaseQuery(query) {
1067
+ const ast = query.ast;
1068
+ if (ast.type === "order") {
1069
+ return Query.fromAst(ast.query);
1070
+ }
1071
+ return query;
1072
+ }
1073
+ /**
1074
+ * Checks if the view has unsaved changes (in-memory sort differs from persisted sort).
1075
+ */
1076
+ getViewDirty() {
1077
+ return this._registry.get(this._viewDirty);
1117
1078
  }
1118
1079
  };
1119
1080
  var editorTextToCellValue = (props, value) => {
1120
1081
  switch (props.format) {
1121
- case FormatEnum2.Ref: {
1122
- if (isLiveObject(value)) {
1082
+ case Format2.TypeFormat.Ref: {
1083
+ if (Obj.isObject(value)) {
1123
1084
  return Ref.make(value);
1124
1085
  } else {
1125
1086
  return value;
1126
1087
  }
1127
1088
  }
1128
- case FormatEnum2.SingleSelect: {
1089
+ case Format2.TypeFormat.SingleSelect: {
1129
1090
  const ids = extractTagIds(value);
1130
1091
  if (ids && ids.length > 0) {
1131
1092
  return ids[0];
@@ -1133,7 +1094,7 @@ var editorTextToCellValue = (props, value) => {
1133
1094
  return value;
1134
1095
  }
1135
1096
  }
1136
- case FormatEnum2.MultiSelect: {
1097
+ case Format2.TypeFormat.MultiSelect: {
1137
1098
  const ids = extractTagIds(value);
1138
1099
  return ids || value;
1139
1100
  }
@@ -1147,104 +1108,40 @@ var editorTextToCellValue = (props, value) => {
1147
1108
  }
1148
1109
  };
1149
1110
 
1150
- // src/model/modal-controller.ts
1151
- import { signal as signal4 } from "@preact/signals-core";
1152
- var ModalController = class {
1153
- _state = signal4({
1154
- type: "closed"
1155
- });
1156
- _triggerRef = {
1157
- current: null
1158
- };
1159
- get state() {
1160
- return this._state;
1161
- }
1162
- get trigger() {
1163
- return this._triggerRef;
1164
- }
1165
- setTrigger = (trigger) => {
1166
- this._triggerRef.current = trigger;
1167
- };
1168
- showRowMenu = (rowIndex) => {
1169
- this._state.value = {
1170
- type: "row",
1171
- rowIndex
1172
- };
1173
- };
1174
- showColumnMenu = (fieldId) => {
1175
- this._state.value = {
1176
- type: "column",
1177
- fieldId
1178
- };
1179
- };
1180
- showColumnCreator = () => {
1181
- this._state.value = {
1182
- type: "columnSettings",
1183
- mode: {
1184
- type: "create"
1185
- }
1186
- };
1187
- };
1188
- showReferencePanel = (targetId, schemaId) => {
1189
- this._state.value = {
1190
- type: "refPanel",
1191
- targetId,
1192
- typename: schemaId
1193
- };
1194
- };
1195
- openColumnSettings = () => {
1196
- if (this._state.value.type === "column") {
1197
- const fieldId = this._state.value.fieldId;
1198
- requestAnimationFrame(() => {
1199
- this._state.value = {
1200
- type: "columnSettings",
1201
- mode: {
1202
- type: "edit",
1203
- fieldId
1204
- }
1205
- };
1206
- });
1207
- }
1208
- };
1209
- close = () => {
1210
- this._state.value = {
1211
- type: "closed"
1212
- };
1213
- };
1214
- };
1215
-
1216
1111
  // src/model/table-presentation.ts
1217
- import { signal as signal5 } from "@preact/signals-core";
1112
+ import { Atom as Atom3 } from "@effect-atom/atom-react";
1218
1113
  import * as Predicate from "effect/Predicate";
1219
- import { Obj as Obj3 } from "@dxos/echo";
1220
- import { FormatEnum as FormatEnum3, TypeEnum as TypeEnum2, getValue as getValue3 } from "@dxos/echo/internal";
1221
- import { cellClassesForFieldType, formatForDisplay as formatForDisplay2 } from "@dxos/react-ui-form";
1114
+ import { Obj as Obj2 } from "@dxos/echo";
1115
+ import { Format as Format3, TypeEnum, getValue as getValue2 } from "@dxos/echo/internal";
1116
+ import { cellClassesForFieldType, formatForDisplay } from "@dxos/react-ui-form";
1222
1117
  import { toPlaneCellIndex } from "@dxos/react-ui-grid";
1223
- import { mx } from "@dxos/react-ui-theme";
1224
1118
  import { VIEW_FIELD_LIMIT } from "@dxos/schema";
1119
+ import { mx } from "@dxos/ui-theme";
1225
1120
  var TablePresentation = class {
1226
1121
  model;
1122
+ _registry;
1227
1123
  _visibleRange;
1228
1124
  fieldProjectionCache = /* @__PURE__ */ new Map();
1229
- constructor(model, _visibleRange = signal5({
1230
- start: {
1231
- row: 0,
1232
- col: 0
1233
- },
1234
- end: {
1235
- row: 0,
1236
- col: 0
1237
- }
1238
- })) {
1125
+ constructor(registry, model) {
1239
1126
  this.model = model;
1240
- this._visibleRange = _visibleRange;
1127
+ this._registry = registry;
1128
+ this._visibleRange = Atom3.make({
1129
+ start: {
1130
+ row: 0,
1131
+ col: 0
1132
+ },
1133
+ end: {
1134
+ row: 0,
1135
+ col: 0
1136
+ }
1137
+ });
1241
1138
  }
1242
1139
  getCells(range, plane) {
1243
1140
  this.fieldProjectionCache.clear();
1244
1141
  let cells;
1245
1142
  switch (plane) {
1246
1143
  case "grid":
1247
- this._visibleRange.value = range;
1144
+ this._registry.set(this._visibleRange, range);
1248
1145
  cells = this.getMainGridCells(range);
1249
1146
  break;
1250
1147
  case "frozenRowsStart":
@@ -1296,7 +1193,7 @@ var TablePresentation = class {
1296
1193
  const { props } = this.getFieldProjection(field.id);
1297
1194
  const cell = {
1298
1195
  get value() {
1299
- const value = getValue3(obj, field.path);
1196
+ const value = getValue2(obj, field.path);
1300
1197
  if (value == null) {
1301
1198
  return "";
1302
1199
  }
@@ -1304,19 +1201,19 @@ var TablePresentation = class {
1304
1201
  return "";
1305
1202
  }
1306
1203
  switch (props.format) {
1307
- case FormatEnum3.Boolean:
1308
- case FormatEnum3.SingleSelect:
1309
- case FormatEnum3.MultiSelect: {
1204
+ case Format3.TypeFormat.Boolean:
1205
+ case Format3.TypeFormat.SingleSelect:
1206
+ case Format3.TypeFormat.MultiSelect: {
1310
1207
  return "";
1311
1208
  }
1312
- case FormatEnum3.Ref: {
1209
+ case Format3.TypeFormat.Ref: {
1313
1210
  if (!field.referencePath) {
1314
1211
  return "";
1315
1212
  }
1316
- return getValue3(value.target, field.referencePath);
1213
+ return getValue2(value.target, field.referencePath);
1317
1214
  }
1318
1215
  default: {
1319
- return formatForDisplay2({
1216
+ return formatForDisplay({
1320
1217
  type: props.type,
1321
1218
  format: props.format,
1322
1219
  value
@@ -1335,8 +1232,8 @@ var TablePresentation = class {
1335
1232
  classes.push(rowSelectionClasses);
1336
1233
  }
1337
1234
  cell.className = mx(classes);
1338
- if (props.type === TypeEnum2.Array) {
1339
- const targetArray = getValue3(obj, field.path);
1235
+ if (props.type === TypeEnum.Array) {
1236
+ const targetArray = getValue2(obj, field.path);
1340
1237
  if (targetArray && Array.isArray(targetArray)) {
1341
1238
  const getLabel = (value) => {
1342
1239
  if (!value) {
@@ -1362,15 +1259,15 @@ var TablePresentation = class {
1362
1259
  cell.accessoryHtml = `<div role='none' class="flex flex-row gap-1 overflow-x-auto">${tags}</div>`;
1363
1260
  }
1364
1261
  }
1365
- if (props.format === FormatEnum3.Ref && props.referenceSchema) {
1366
- const targetObj = getValue3(obj, field.path)?.target;
1262
+ if (props.format === Format3.TypeFormat.Ref && props.referenceSchema) {
1263
+ const targetObj = getValue2(obj, field.path)?.target;
1367
1264
  if (targetObj) {
1368
- const dxn = Obj3.getDXN(targetObj)?.toString();
1369
- cell.accessoryHtml = `<div role="none" class="absolute inline-end-0 inset-block-0 p-[--dx-grid-cell-content-padding-block]"><dx-anchor refId=${dxn} class="dx-button is-6 aspect-square min-bs-0" data-dx-grid-action="accessory"><dx-icon icon="ph--link-simple--regular"/></dx-anchor></div>`;
1265
+ const dxn = Obj2.getDXN(targetObj)?.toString();
1266
+ cell.accessoryHtml = `<div role="none" class="absolute end-0 inset-y-0 p-(--dx-grid-cell-content-padding-block)"><dx-anchor dxn=${dxn} class="dx-button w-6 aspect-square min-h-0" data-dx-grid-action="accessory"><dx-icon icon="ph--link-simple--regular"/></dx-anchor></div>`;
1370
1267
  }
1371
1268
  }
1372
- if (props.format === FormatEnum3.Boolean) {
1373
- const value = getValue3(obj, field.path);
1269
+ if (props.format === Format3.TypeFormat.Boolean) {
1270
+ const value = getValue2(obj, field.path);
1374
1271
  cell.accessoryHtml = tableControls.switch.render({
1375
1272
  colIndex,
1376
1273
  rowIndex: displayIndex,
@@ -1378,8 +1275,8 @@ var TablePresentation = class {
1378
1275
  });
1379
1276
  cell.readonly = "no-text-select";
1380
1277
  }
1381
- if (props.format === FormatEnum3.SingleSelect) {
1382
- const value = getValue3(obj, field.path);
1278
+ if (props.format === Format3.TypeFormat.SingleSelect) {
1279
+ const value = getValue2(obj, field.path);
1383
1280
  const options = this.model.projection.getFieldProjection(field.id).props.options;
1384
1281
  if (options) {
1385
1282
  const option = options.find((o) => o.id === value);
@@ -1388,8 +1285,8 @@ var TablePresentation = class {
1388
1285
  }
1389
1286
  }
1390
1287
  }
1391
- if (props.format === FormatEnum3.MultiSelect) {
1392
- const values = getValue3(obj, field.path);
1288
+ if (props.format === Format3.TypeFormat.MultiSelect) {
1289
+ const values = getValue2(obj, field.path);
1393
1290
  const options = this.model.projection.getFieldProjection(field.id).props.options;
1394
1291
  if (options && values && values.length > 0) {
1395
1292
  const tags = values.map((value) => {
@@ -1412,22 +1309,22 @@ var TablePresentation = class {
1412
1309
  }
1413
1310
  getMainGridCells(range) {
1414
1311
  const cells = {};
1415
- const fields = this.model.projection?.fields ?? [];
1312
+ const fields = this.model.projection?.getFields() ?? [];
1416
1313
  for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
1417
1314
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1418
1315
  const field = fields[col];
1419
1316
  if (!field) {
1420
1317
  continue;
1421
1318
  }
1422
- this.createDataCell(cells, this.model.rows.value[row], field, col, row);
1319
+ this.createDataCell(cells, this.model.getRows()[row], field, col, row);
1423
1320
  }
1424
1321
  }
1425
1322
  return cells;
1426
1323
  }
1427
1324
  getDraftRowCells(range) {
1428
1325
  const cells = {};
1429
- const fields = this.model.projection?.fields ?? [];
1430
- const draftRows = this.model.draftRows.value;
1326
+ const fields = this.model.projection?.getFields() ?? [];
1327
+ const draftRows = this.model.getDraftRows();
1431
1328
  if (draftRows.length === 0) {
1432
1329
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1433
1330
  cells[toPlaneCellIndex({
@@ -1467,11 +1364,11 @@ var TablePresentation = class {
1467
1364
  }
1468
1365
  getHeaderCells(range) {
1469
1366
  const cells = {};
1470
- const fields = this.model.projection?.fields ?? [];
1367
+ const fields = this.model.projection?.getFields() ?? [];
1368
+ const currentSort = this.model.getSorting();
1471
1369
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1472
1370
  const { field, props } = this.model.projection.getFieldProjection(fields[col].id);
1473
- const sorting = this.model.sorting?.sorting;
1474
- const direction = sorting?.fieldId === field.id ? sorting.direction : void 0;
1371
+ const direction = currentSort?.fieldId === field.id ? currentSort.direction : void 0;
1475
1372
  cells[toPlaneCellIndex({
1476
1373
  col,
1477
1374
  row: 0
@@ -1480,7 +1377,7 @@ var TablePresentation = class {
1480
1377
  value: "",
1481
1378
  resizeHandle: "col",
1482
1379
  accessoryHtml: `
1483
- <span class="grow min-is-0 truncate">${props.title ?? field.path}</span>
1380
+ <span class="grow min-w-0 truncate">${props.title ?? field.path}</span>
1484
1381
  ${direction !== void 0 ? tableButtons.sort.render({
1485
1382
  fieldId: field.id,
1486
1383
  direction
@@ -1489,7 +1386,7 @@ var TablePresentation = class {
1489
1386
  fieldId: field.id
1490
1387
  })}
1491
1388
  `,
1492
- className: "!bg-toolbarSurface !text-description [&>div]:flex [&>div]:items-stretch"
1389
+ className: "!bg-toolbar-surface text-description! [&>div]:flex [&>div]:items-stretch"
1493
1390
  };
1494
1391
  }
1495
1392
  return cells;
@@ -1540,7 +1437,7 @@ var TablePresentation = class {
1540
1437
  col: 0,
1541
1438
  row: 0
1542
1439
  })]: {
1543
- className: "!bg-toolbarSurface",
1440
+ className: "!bg-toolbar-surface",
1544
1441
  readonly: true,
1545
1442
  value: ""
1546
1443
  }
@@ -1554,9 +1451,9 @@ var TablePresentation = class {
1554
1451
  accessoryHtml: tableControls.checkbox.render({
1555
1452
  rowIndex: 0,
1556
1453
  header: true,
1557
- checked: this.model.selection.allRowsSeleted.value
1454
+ checked: this.model.selection.allRowsSelected
1558
1455
  }),
1559
- className: "!bg-toolbarSurface",
1456
+ className: "!bg-toolbar-surface",
1560
1457
  readonly: true,
1561
1458
  value: ""
1562
1459
  }
@@ -1569,9 +1466,9 @@ var TablePresentation = class {
1569
1466
  row: 0
1570
1467
  })]: {
1571
1468
  accessoryHtml: this.model.features.schemaEditable ? tableButtons.addColumn.render({
1572
- disabled: (this.model.projection?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT
1469
+ disabled: (this.model.projection?.getFields()?.length ?? 0) >= VIEW_FIELD_LIMIT
1573
1470
  }) : void 0,
1574
- className: "!bg-toolbarSurface",
1471
+ className: "!bg-toolbar-surface",
1575
1472
  readonly: true,
1576
1473
  value: ""
1577
1474
  }
@@ -1579,7 +1476,7 @@ var TablePresentation = class {
1579
1476
  }
1580
1477
  getDraftActionCells(range) {
1581
1478
  const cells = {};
1582
- const draftRows = this.model.draftRows.value;
1479
+ const draftRows = this.model.getDraftRows();
1583
1480
  if (draftRows.length === 0) {
1584
1481
  cells[toPlaneCellIndex({
1585
1482
  col: 0,
@@ -1610,7 +1507,7 @@ var TablePresentation = class {
1610
1507
  }
1611
1508
  getDraftIconCells(range) {
1612
1509
  const cells = {};
1613
- const draftRows = this.model.draftRows.value;
1510
+ const draftRows = this.model.getDraftRows();
1614
1511
  for (let row = range.start.row; row <= range.end.row; row++) {
1615
1512
  cells[toPlaneCellIndex({
1616
1513
  col: 0,
@@ -1629,7 +1526,7 @@ var cellClassesForRowSelection = (selected, selectionMode) => {
1629
1526
  if (!selected) {
1630
1527
  if (selectionMode === "single") {
1631
1528
  return [
1632
- "!cursor-pointer"
1529
+ "cursor-pointer!"
1633
1530
  ];
1634
1531
  } else {
1635
1532
  return void 0;
@@ -1638,111 +1535,23 @@ var cellClassesForRowSelection = (selected, selectionMode) => {
1638
1535
  switch (selectionMode) {
1639
1536
  case "single":
1640
1537
  return [
1641
- "!bg-currentRelated dx-grid__cell--no-focus-unfurl hover:bg-hoverSurface !cursor-pointer"
1538
+ "dx-grid__cell--no-focus-unfurl bg-current-surface! hover:bg-hover-surface cursor-pointer!"
1642
1539
  ];
1643
1540
  case "multiple":
1644
1541
  return [
1645
- "!bg-gridCellSelected"
1542
+ "bg-grid-cell-selected!"
1646
1543
  ];
1647
1544
  }
1648
1545
  };
1649
1546
 
1650
- // src/hooks/useTableModel.ts
1651
- import { useSignals as _useSignals } from "@preact-signals/safe-react/tracking";
1652
- import { effect as effect3 } from "@preact/signals-core";
1653
- import orderBy2 from "lodash.orderby";
1654
- import { useEffect, useMemo, useState } from "react";
1655
- import { fullyQualifiedId as fullyQualifiedId2 } from "@dxos/react-client/echo";
1656
- import { useSelected, useSelectionActions } from "@dxos/react-ui-attention";
1657
- import { isNonNullable } from "@dxos/util";
1658
- var useTableModel = ({ view, schema, projection, rows, rowActions, features, onSelectionChanged, onRowAction, ...props }) => {
1659
- var _effect = _useSignals();
1660
- try {
1661
- const selected = useSelected(view && fullyQualifiedId2(view), "multi");
1662
- const initialSelection = useMemo(() => selected, [
1663
- view
1664
- ]);
1665
- const [model, setModel] = useState();
1666
- useEffect(() => {
1667
- if (!view || !schema) {
1668
- return;
1669
- }
1670
- let model2;
1671
- const t = setTimeout(async () => {
1672
- model2 = new TableModel({
1673
- view,
1674
- schema,
1675
- projection,
1676
- features,
1677
- rowActions,
1678
- initialSelection,
1679
- onRowAction,
1680
- ...props
1681
- });
1682
- await model2.open();
1683
- setModel(model2);
1684
- });
1685
- return () => {
1686
- clearTimeout(t);
1687
- void model2?.close();
1688
- };
1689
- }, [
1690
- view,
1691
- schema,
1692
- projection,
1693
- features,
1694
- rowActions,
1695
- initialSelection
1696
- ]);
1697
- useEffect(() => {
1698
- if (rows) {
1699
- const sortedRows = orderBy2(rows, [
1700
- (row) => String(row.id)
1701
- ], [
1702
- "asc"
1703
- ]);
1704
- model?.setRows(sortedRows);
1705
- }
1706
- }, [
1707
- model,
1708
- rows
1709
- ]);
1710
- const { multiSelect, clear } = useSelectionActions([
1711
- model?.id
1712
- ].filter(isNonNullable));
1713
- useEffect(() => {
1714
- if (!model) {
1715
- return;
1716
- }
1717
- const unsubscribe = effect3(() => {
1718
- const selectedItems = [
1719
- ...model.selection.selection.value
1720
- ];
1721
- multiSelect(selectedItems);
1722
- onSelectionChanged?.(selectedItems);
1723
- });
1724
- return () => {
1725
- clear();
1726
- unsubscribe();
1727
- };
1728
- }, [
1729
- model,
1730
- onSelectionChanged
1731
- ]);
1732
- return model;
1733
- } finally {
1734
- _effect.f();
1735
- }
1736
- };
1737
-
1738
1547
  // src/hooks/useAddRow.ts
1739
1548
  import { useCallback } from "react";
1740
- import { Obj as Obj4 } from "@dxos/echo";
1741
- var useAddRow = ({ space, schema }) => {
1549
+ import { Obj as Obj3 } from "@dxos/echo";
1550
+ var useAddRow = ({ db, schema }) => {
1742
1551
  return useCallback((data) => {
1743
- if (space && schema) {
1552
+ if (db && schema) {
1744
1553
  try {
1745
- space.db.add(Obj4.make(schema, data ?? {}));
1554
+ db.add(Obj3.make(schema, data ?? {}));
1746
1555
  return "final";
1747
1556
  } catch (error) {
1748
1557
  return "draft";
@@ -1750,27 +1559,137 @@ var useAddRow = ({ space, schema }) => {
1750
1559
  }
1751
1560
  return "final";
1752
1561
  }, [
1753
- space,
1562
+ db,
1754
1563
  schema
1755
1564
  ]);
1756
1565
  };
1757
1566
 
1567
+ // src/hooks/useProjectionModel.ts
1568
+ import { useState } from "react";
1569
+ import { JsonSchema, Type } from "@dxos/echo";
1570
+ import { useAsyncEffect } from "@dxos/react-ui";
1571
+ import { ProjectionModel, createEchoChangeCallback as createEchoChangeCallback2 } from "@dxos/schema";
1572
+ var useProjectionModel = (schema, table, registry) => {
1573
+ const [projection, setProjection] = useState();
1574
+ useAsyncEffect(async () => {
1575
+ if (schema && table) {
1576
+ const view = await table.view.load();
1577
+ const jsonSchema = Type.isMutable(schema) ? schema.jsonSchema : JsonSchema.toJsonSchema(schema);
1578
+ const change = createEchoChangeCallback2(view, Type.isMutable(schema) ? schema : void 0);
1579
+ const projection2 = new ProjectionModel({
1580
+ registry,
1581
+ view,
1582
+ baseSchema: jsonSchema,
1583
+ change
1584
+ });
1585
+ projection2.normalizeView();
1586
+ setProjection(projection2);
1587
+ }
1588
+ }, [
1589
+ schema,
1590
+ table,
1591
+ registry
1592
+ ]);
1593
+ return projection;
1594
+ };
1595
+
1596
+ // src/hooks/useTableModel.ts
1597
+ import { RegistryContext } from "@effect-atom/atom-react";
1598
+ import { useContext, useEffect, useMemo, useState as useState2 } from "react";
1599
+ import { Obj as Obj4 } from "@dxos/echo";
1600
+ import { useSelected, useSelectionActions } from "@dxos/react-ui-attention";
1601
+ import { isNonNullable } from "@dxos/util";
1602
+ var useTableModel = ({ object, projection, db, rows, rowActions, features, onSelectionChanged, onRowAction, ...props }) => {
1603
+ const registry = useContext(RegistryContext);
1604
+ const selected = useSelected(object && Obj4.getDXN(object).toString(), "multi");
1605
+ const initialSelection = useMemo(() => selected, [
1606
+ object
1607
+ ]);
1608
+ const [model, setModel] = useState2();
1609
+ useEffect(() => {
1610
+ if (!object || !projection) {
1611
+ return;
1612
+ }
1613
+ let model2;
1614
+ const t = setTimeout(async () => {
1615
+ model2 = new TableModel({
1616
+ registry,
1617
+ object,
1618
+ projection,
1619
+ db,
1620
+ change: createEchoChangeCallback(object),
1621
+ features,
1622
+ rowActions,
1623
+ initialSelection,
1624
+ onRowAction,
1625
+ ...props
1626
+ });
1627
+ await model2.open();
1628
+ setModel(model2);
1629
+ });
1630
+ return () => {
1631
+ clearTimeout(t);
1632
+ void model2?.close();
1633
+ };
1634
+ }, [
1635
+ registry,
1636
+ object,
1637
+ projection,
1638
+ features,
1639
+ rowActions,
1640
+ initialSelection
1641
+ ]);
1642
+ useEffect(() => {
1643
+ if (rows && model) {
1644
+ model.setRows(rows);
1645
+ }
1646
+ }, [
1647
+ model,
1648
+ rows
1649
+ ]);
1650
+ const { multiSelect, clear } = useSelectionActions([
1651
+ model?.id
1652
+ ].filter(isNonNullable));
1653
+ useEffect(() => {
1654
+ if (!model) {
1655
+ return;
1656
+ }
1657
+ const unsubscribe = registry.subscribe(model.selection.selectionAtom, () => {
1658
+ const selectedItems = [
1659
+ ...model.selection.selection
1660
+ ];
1661
+ multiSelect(selectedItems);
1662
+ onSelectionChanged?.(selectedItems);
1663
+ });
1664
+ return () => {
1665
+ clear();
1666
+ unsubscribe();
1667
+ };
1668
+ }, [
1669
+ registry,
1670
+ model,
1671
+ onSelectionChanged
1672
+ ]);
1673
+ return model;
1674
+ };
1675
+
1758
1676
  export {
1759
- getBaseSchema,
1760
- makeDynamicTable,
1761
- touch,
1677
+ extractOrder,
1678
+ getSortValue,
1679
+ compareValues,
1762
1680
  BUTTON_IDENTIFIERS,
1763
1681
  tableButtons,
1764
1682
  CONTROL_IDENTIFIERS,
1765
1683
  CheckboxStory,
1766
1684
  SwitchStory,
1767
1685
  tableControls,
1768
- narrowSchema,
1686
+ createEchoChangeCallback,
1687
+ createDirectChangeCallback,
1769
1688
  TableModel,
1770
- ModalController,
1771
1689
  TablePresentation,
1772
1690
  cellClassesForRowSelection,
1773
- useTableModel,
1774
- useAddRow
1691
+ useAddRow,
1692
+ useProjectionModel,
1693
+ useTableModel
1775
1694
  };
1776
- //# sourceMappingURL=chunk-G7MGPBSR.mjs.map
1695
+ //# sourceMappingURL=chunk-YDXPC7HA.mjs.map