@dxos/react-ui-table 0.8.4-main.3c1ae3b → 0.8.4-main.3fbcb4aa9b

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