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

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