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