@dxos/react-ui-table 0.8.4-main.72ec0f3 → 0.8.4-main.937b3ca

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