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