@dxos/react-ui-table 0.8.4-main.7ace549 → 0.8.4-main.937b3ca

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/lib/browser/{chunk-KNC7XYT6.mjs → chunk-MNZKOFMA.mjs} +597 -634
  2. package/dist/lib/browser/chunk-MNZKOFMA.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-5XWSB3JP.mjs → chunk-OZBLY7XM.mjs} +18 -13
  4. package/dist/lib/browser/chunk-OZBLY7XM.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +1096 -980
  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 +19 -18
  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-CWSGS3Y2.mjs → chunk-2ATAIIAO.mjs} +597 -633
  12. package/dist/lib/node-esm/chunk-2ATAIIAO.mjs.map +7 -0
  13. package/dist/lib/node-esm/{chunk-MKTMOY46.mjs → chunk-WNZSKIRF.mjs} +18 -13
  14. package/dist/lib/node-esm/chunk-WNZSKIRF.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +1096 -980
  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 +19 -18
  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 +1 -2
  22. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  23. package/dist/types/src/components/Table/ColumnSettings.d.ts +2 -2
  24. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  25. package/dist/types/src/components/Table/DynamicTable.d.ts +4 -5
  26. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +2 -16
  28. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/Relations.stories.d.ts +6 -3
  30. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
  31. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  32. package/dist/types/src/components/Table/Table.d.ts +8 -8
  33. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  34. package/dist/types/src/components/Table/Table.stories.d.ts +6 -3
  35. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  36. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +1 -1
  37. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +10 -12
  38. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  39. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +5 -7
  40. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  41. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +2 -2
  42. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  43. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +2 -2
  44. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
  45. package/dist/types/src/components/testing.d.ts +22 -13
  46. package/dist/types/src/components/testing.d.ts.map +1 -1
  47. package/dist/types/src/hooks/useAddRow.d.ts +5 -6
  48. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  49. package/dist/types/src/hooks/useProjectionModel.d.ts +2 -1
  50. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -1
  51. package/dist/types/src/hooks/useTableModel.d.ts +5 -4
  52. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  53. package/dist/types/src/model/modal-controller.d.ts +5 -1
  54. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  55. package/dist/types/src/model/selection-model.d.ts +17 -8
  56. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  57. package/dist/types/src/model/table-model.d.ts +97 -16
  58. package/dist/types/src/model/table-model.d.ts.map +1 -1
  59. package/dist/types/src/model/table-presentation.d.ts +3 -1
  60. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  61. package/dist/types/src/testing/useTestTableModel.d.ts +5 -8
  62. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  63. package/dist/types/src/translations.d.ts +2 -2
  64. package/dist/types/src/translations.d.ts.map +1 -1
  65. package/dist/types/src/types/Table.d.ts +17 -14
  66. package/dist/types/src/types/Table.d.ts.map +1 -1
  67. package/dist/types/src/util/dynamic-table.d.ts +8 -7
  68. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  69. package/dist/types/src/util/index.d.ts +2 -0
  70. package/dist/types/src/util/index.d.ts.map +1 -1
  71. package/dist/types/src/util/query-ast.d.ts +7 -0
  72. package/dist/types/src/util/query-ast.d.ts.map +1 -0
  73. package/dist/types/src/util/sort.d.ts +12 -0
  74. package/dist/types/src/util/sort.d.ts.map +1 -0
  75. package/dist/types/tsconfig.tsbuildinfo +1 -1
  76. package/package.json +53 -54
  77. package/src/components/Table/ColumnActionsMenu.tsx +10 -6
  78. package/src/components/Table/ColumnSettings.tsx +6 -6
  79. package/src/components/Table/DynamicTable.stories.tsx +6 -7
  80. package/src/components/Table/DynamicTable.tsx +21 -22
  81. package/src/components/Table/Relations.stories.tsx +23 -23
  82. package/src/components/Table/RowActionsMenu.tsx +3 -2
  83. package/src/components/Table/Table.stories.tsx +34 -43
  84. package/src/components/Table/Table.tsx +335 -305
  85. package/src/components/TableCellEditor/CellValidationMessage.tsx +1 -1
  86. package/src/components/TableCellEditor/FormCellEditor.tsx +111 -134
  87. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +7 -5
  88. package/src/components/TableCellEditor/TableCellEditor.tsx +27 -27
  89. package/src/components/TableToolbar/TableToolbar.tsx +18 -12
  90. package/src/components/testing.ts +37 -11
  91. package/src/hooks/useAddRow.ts +8 -10
  92. package/src/hooks/useProjectionModel.ts +29 -8
  93. package/src/hooks/useTableModel.ts +26 -22
  94. package/src/model/modal-controller.ts +23 -12
  95. package/src/model/selection-model.ts +75 -44
  96. package/src/model/table-model.test.ts +80 -52
  97. package/src/model/table-model.ts +402 -131
  98. package/src/model/table-presentation.test.ts +57 -47
  99. package/src/model/table-presentation.ts +22 -17
  100. package/src/playwright/TableManager.ts +2 -2
  101. package/src/playwright/playwright.config.ts +1 -1
  102. package/src/playwright/smoke.spec.ts +3 -2
  103. package/src/testing/useTestTableModel.ts +23 -24
  104. package/src/translations.ts +1 -1
  105. package/src/types/Table.ts +19 -18
  106. package/src/util/dynamic-table.ts +28 -12
  107. package/src/util/index.ts +2 -0
  108. package/src/util/query-ast.ts +19 -0
  109. package/src/util/sort.ts +90 -0
  110. package/dist/lib/browser/chunk-5XWSB3JP.mjs.map +0 -7
  111. package/dist/lib/browser/chunk-KNC7XYT6.mjs.map +0 -7
  112. package/dist/lib/node-esm/chunk-CWSGS3Y2.mjs.map +0 -7
  113. package/dist/lib/node-esm/chunk-MKTMOY46.mjs.map +0 -7
  114. package/dist/types/src/model/table-sorting.d.ts +0 -56
  115. package/dist/types/src/model/table-sorting.d.ts.map +0 -1
  116. 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-MKTMOY46.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 = ({ typename, properties, jsonSchema, schema }) => {
10
- if (typename && properties) {
11
- const schema2 = getSchemaFromPropertyDefinitions(typename, properties);
12
- return {
13
- typename: schema2.typename,
14
- jsonSchema: schema2.jsonSchema
15
- };
16
- } else if (schema) {
17
- return {
18
- typename: 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,18 +404,12 @@ 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
415
  _onDeleteColumn;
@@ -593,43 +418,115 @@ var TableModel = class extends Resource2 {
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
441
  }, rowActions = [], onCellUpdate, onDeleteColumn, 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 [sort] = sorting;
630
- this._sorting.setSort(sort.fieldId, sort.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;
@@ -640,13 +537,13 @@ var TableModel = class extends Resource2 {
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,11 +905,10 @@ 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",
@@ -975,10 +917,10 @@ var TableModel = class extends Resource2 {
975
917
  });
976
918
  const snapshot = getSnapshot(currentRow);
977
919
  setValue(snapshot, field.path, transformedValue);
978
- const schema = Obj2.getSchema(currentRow);
920
+ const schema = Obj.getSchema(currentRow);
979
921
  invariant(schema, void 0, {
980
922
  F: __dxlog_file,
981
- L: 522,
923
+ L: 698,
982
924
  S: this,
983
925
  A: [
984
926
  "schema",
@@ -990,7 +932,7 @@ var TableModel = class extends Resource2 {
990
932
  const error = validationResult[0];
991
933
  invariant(error.path === field.path, void 0, {
992
934
  F: __dxlog_file,
993
- L: 527,
935
+ L: 703,
994
936
  S: this,
995
937
  A: [
996
938
  "error.path === field.path",
@@ -1009,24 +951,27 @@ var TableModel = class extends Resource2 {
1009
951
  };
1010
952
  setCellData = (cell, value) => {
1011
953
  const { col, row, plane } = cell;
1012
- const fields = this._projection?.fields ?? [];
954
+ const fields = this._projection?.getFields() ?? [];
1013
955
  if (col < 0 || col >= fields.length) {
1014
956
  return;
1015
957
  }
1016
958
  const field = fields[col];
1017
959
  const { props } = this._projection.getFieldProjection(field.id);
1018
960
  const transformedValue = editorTextToCellValue(props, value);
1019
- if (props.format === Format2.TypeFormat.Ref && !isLiveObject(value)) {
961
+ if (props.format === Format2.TypeFormat.Ref && !Obj.isObject(value)) {
1020
962
  return;
1021
963
  }
1022
964
  if (plane === "frozenRowsEnd") {
1023
- if (row >= 0 && row < this._draftRows.value.length) {
1024
- 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);
1025
967
  this.validateDraftRow(row);
1026
968
  }
1027
969
  } else {
1028
- const rowIdx = this._sorting.getDataIndex(row);
1029
- 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
+ });
1030
975
  }
1031
976
  };
1032
977
  /**
@@ -1035,12 +980,15 @@ var TableModel = class extends Resource2 {
1035
980
  * @param {(value: any) => any} update - A function that takes the current value and returns the updated value.
1036
981
  */
1037
982
  updateCellData({ col, row }, update) {
1038
- const dataRow = this._sorting.getDataIndex(row);
1039
- const fields = this._projection?.fields ?? [];
983
+ const fields = this._projection?.getFields() ?? [];
1040
984
  const field = fields[col];
1041
- 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);
1042
988
  const updatedValue = update(value);
1043
- setValue(this._rows.value[dataRow], field.path, updatedValue);
989
+ this._change.row(rowData, (mutableRow) => {
990
+ setValue(mutableRow, field.path, updatedValue);
991
+ });
1044
992
  }
1045
993
  /**
1046
994
  * Gets the domain-appropriate cell type for a given cell position.
@@ -1074,7 +1022,7 @@ var TableModel = class extends Resource2 {
1074
1022
  if (!this._projection) {
1075
1023
  return;
1076
1024
  }
1077
- const field = this._projection?.fields.find((field2) => field2.id === fieldId);
1025
+ const field = this._projection?.getFields().find((field2) => field2.id === fieldId);
1078
1026
  if (field && this._onDeleteColumn) {
1079
1027
  this._onDeleteColumn(field.id);
1080
1028
  }
@@ -1083,12 +1031,14 @@ var TableModel = class extends Resource2 {
1083
1031
  // Resize
1084
1032
  //
1085
1033
  setColumnWidth(columnIndex, width) {
1086
- const fields = this._projection?.fields ?? [];
1034
+ const fields = this._projection?.getFields() ?? [];
1087
1035
  if (columnIndex < fields.length) {
1088
1036
  const newWidth = Math.max(0, width);
1089
1037
  const field = fields[columnIndex];
1090
1038
  if (field) {
1091
- this.table.sizes[field.path] = newWidth;
1039
+ this._change.table((mutableTable) => {
1040
+ mutableTable.sizes[field.path] = newWidth;
1041
+ });
1092
1042
  }
1093
1043
  }
1094
1044
  }
@@ -1104,17 +1054,87 @@ var TableModel = class extends Resource2 {
1104
1054
  this._pinnedRows.bottom = this._pinnedRows.bottom.filter((index) => index !== rowIndex);
1105
1055
  }
1106
1056
  //
1107
- // View operations
1057
+ // Sort operations
1108
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
+ */
1109
1095
  saveView() {
1110
- 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);
1111
1131
  }
1112
1132
  };
1113
1133
  var editorTextToCellValue = (props, value) => {
1114
1134
  switch (props.format) {
1115
1135
  case Format2.TypeFormat.Ref: {
1116
- if (isLiveObject(value)) {
1117
- return Ref2.make(value);
1136
+ if (Obj.isObject(value)) {
1137
+ return Ref.make(value);
1118
1138
  } else {
1119
1139
  return value;
1120
1140
  }
@@ -1141,104 +1161,40 @@ var editorTextToCellValue = (props, value) => {
1141
1161
  }
1142
1162
  };
1143
1163
 
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
1164
  // src/model/table-presentation.ts
1211
- import { signal as signal5 } from "@preact/signals-core";
1165
+ import { Atom as Atom3 } from "@effect-atom/atom-react";
1212
1166
  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";
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";
1216
1170
  import { toPlaneCellIndex } from "@dxos/react-ui-grid";
1217
- import { mx } from "@dxos/react-ui-theme";
1218
1171
  import { VIEW_FIELD_LIMIT } from "@dxos/schema";
1172
+ import { mx } from "@dxos/ui-theme";
1219
1173
  var TablePresentation = class {
1220
1174
  model;
1175
+ _registry;
1221
1176
  _visibleRange;
1222
1177
  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
- })) {
1178
+ constructor(registry, model) {
1233
1179
  this.model = model;
1234
- 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
+ });
1235
1191
  }
1236
1192
  getCells(range, plane) {
1237
1193
  this.fieldProjectionCache.clear();
1238
1194
  let cells;
1239
1195
  switch (plane) {
1240
1196
  case "grid":
1241
- this._visibleRange.value = range;
1197
+ this._registry.set(this._visibleRange, range);
1242
1198
  cells = this.getMainGridCells(range);
1243
1199
  break;
1244
1200
  case "frozenRowsStart":
@@ -1290,7 +1246,7 @@ var TablePresentation = class {
1290
1246
  const { props } = this.getFieldProjection(field.id);
1291
1247
  const cell = {
1292
1248
  get value() {
1293
- const value = getValue3(obj, field.path);
1249
+ const value = getValue2(obj, field.path);
1294
1250
  if (value == null) {
1295
1251
  return "";
1296
1252
  }
@@ -1307,10 +1263,10 @@ var TablePresentation = class {
1307
1263
  if (!field.referencePath) {
1308
1264
  return "";
1309
1265
  }
1310
- return getValue3(value.target, field.referencePath);
1266
+ return getValue2(value.target, field.referencePath);
1311
1267
  }
1312
1268
  default: {
1313
- return formatForDisplay2({
1269
+ return formatForDisplay({
1314
1270
  type: props.type,
1315
1271
  format: props.format,
1316
1272
  value
@@ -1329,8 +1285,8 @@ var TablePresentation = class {
1329
1285
  classes.push(rowSelectionClasses);
1330
1286
  }
1331
1287
  cell.className = mx(classes);
1332
- if (props.type === TypeEnum2.Array) {
1333
- const targetArray = getValue3(obj, field.path);
1288
+ if (props.type === TypeEnum.Array) {
1289
+ const targetArray = getValue2(obj, field.path);
1334
1290
  if (targetArray && Array.isArray(targetArray)) {
1335
1291
  const getLabel = (value) => {
1336
1292
  if (!value) {
@@ -1357,14 +1313,14 @@ var TablePresentation = class {
1357
1313
  }
1358
1314
  }
1359
1315
  if (props.format === Format3.TypeFormat.Ref && props.referenceSchema) {
1360
- const targetObj = getValue3(obj, field.path)?.target;
1316
+ const targetObj = getValue2(obj, field.path)?.target;
1361
1317
  if (targetObj) {
1362
- const dxn = Obj3.getDXN(targetObj)?.toString();
1318
+ const dxn = Obj2.getDXN(targetObj)?.toString();
1363
1319
  cell.accessoryHtml = `<div role="none" class="absolute inline-end-0 inset-block-0 p-[--dx-grid-cell-content-padding-block]"><dx-anchor refId=${dxn} class="dx-button is-6 aspect-square min-bs-0" data-dx-grid-action="accessory"><dx-icon icon="ph--link-simple--regular"/></dx-anchor></div>`;
1364
1320
  }
1365
1321
  }
1366
1322
  if (props.format === Format3.TypeFormat.Boolean) {
1367
- const value = getValue3(obj, field.path);
1323
+ const value = getValue2(obj, field.path);
1368
1324
  cell.accessoryHtml = tableControls.switch.render({
1369
1325
  colIndex,
1370
1326
  rowIndex: displayIndex,
@@ -1373,7 +1329,7 @@ var TablePresentation = class {
1373
1329
  cell.readonly = "no-text-select";
1374
1330
  }
1375
1331
  if (props.format === Format3.TypeFormat.SingleSelect) {
1376
- const value = getValue3(obj, field.path);
1332
+ const value = getValue2(obj, field.path);
1377
1333
  const options = this.model.projection.getFieldProjection(field.id).props.options;
1378
1334
  if (options) {
1379
1335
  const option = options.find((o) => o.id === value);
@@ -1383,7 +1339,7 @@ var TablePresentation = class {
1383
1339
  }
1384
1340
  }
1385
1341
  if (props.format === Format3.TypeFormat.MultiSelect) {
1386
- const values = getValue3(obj, field.path);
1342
+ const values = getValue2(obj, field.path);
1387
1343
  const options = this.model.projection.getFieldProjection(field.id).props.options;
1388
1344
  if (options && values && values.length > 0) {
1389
1345
  const tags = values.map((value) => {
@@ -1406,22 +1362,22 @@ var TablePresentation = class {
1406
1362
  }
1407
1363
  getMainGridCells(range) {
1408
1364
  const cells = {};
1409
- const fields = this.model.projection?.fields ?? [];
1365
+ const fields = this.model.projection?.getFields() ?? [];
1410
1366
  for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
1411
1367
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1412
1368
  const field = fields[col];
1413
1369
  if (!field) {
1414
1370
  continue;
1415
1371
  }
1416
- this.createDataCell(cells, this.model.rows.value[row], field, col, row);
1372
+ this.createDataCell(cells, this.model.getRows()[row], field, col, row);
1417
1373
  }
1418
1374
  }
1419
1375
  return cells;
1420
1376
  }
1421
1377
  getDraftRowCells(range) {
1422
1378
  const cells = {};
1423
- const fields = this.model.projection?.fields ?? [];
1424
- const draftRows = this.model.draftRows.value;
1379
+ const fields = this.model.projection?.getFields() ?? [];
1380
+ const draftRows = this.model.getDraftRows();
1425
1381
  if (draftRows.length === 0) {
1426
1382
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1427
1383
  cells[toPlaneCellIndex({
@@ -1461,11 +1417,11 @@ var TablePresentation = class {
1461
1417
  }
1462
1418
  getHeaderCells(range) {
1463
1419
  const cells = {};
1464
- const fields = this.model.projection?.fields ?? [];
1420
+ const fields = this.model.projection?.getFields() ?? [];
1421
+ const currentSort = this.model.getSorting();
1465
1422
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1466
1423
  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;
1424
+ const direction = currentSort?.fieldId === field.id ? currentSort.direction : void 0;
1469
1425
  cells[toPlaneCellIndex({
1470
1426
  col,
1471
1427
  row: 0
@@ -1548,7 +1504,7 @@ var TablePresentation = class {
1548
1504
  accessoryHtml: tableControls.checkbox.render({
1549
1505
  rowIndex: 0,
1550
1506
  header: true,
1551
- checked: this.model.selection.allRowsSeleted.value
1507
+ checked: this.model.selection.allRowsSelected
1552
1508
  }),
1553
1509
  className: "!bg-toolbarSurface",
1554
1510
  readonly: true,
@@ -1563,7 +1519,7 @@ var TablePresentation = class {
1563
1519
  row: 0
1564
1520
  })]: {
1565
1521
  accessoryHtml: this.model.features.schemaEditable ? tableButtons.addColumn.render({
1566
- disabled: (this.model.projection?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT
1522
+ disabled: (this.model.projection?.getFields()?.length ?? 0) >= VIEW_FIELD_LIMIT
1567
1523
  }) : void 0,
1568
1524
  className: "!bg-toolbarSurface",
1569
1525
  readonly: true,
@@ -1573,7 +1529,7 @@ var TablePresentation = class {
1573
1529
  }
1574
1530
  getDraftActionCells(range) {
1575
1531
  const cells = {};
1576
- const draftRows = this.model.draftRows.value;
1532
+ const draftRows = this.model.getDraftRows();
1577
1533
  if (draftRows.length === 0) {
1578
1534
  cells[toPlaneCellIndex({
1579
1535
  col: 0,
@@ -1604,7 +1560,7 @@ var TablePresentation = class {
1604
1560
  }
1605
1561
  getDraftIconCells(range) {
1606
1562
  const cells = {};
1607
- const draftRows = this.model.draftRows.value;
1563
+ const draftRows = this.model.getDraftRows();
1608
1564
  for (let row = range.start.row; row <= range.end.row; row++) {
1609
1565
  cells[toPlaneCellIndex({
1610
1566
  col: 0,
@@ -1643,12 +1599,12 @@ var cellClassesForRowSelection = (selected, selectionMode) => {
1643
1599
 
1644
1600
  // src/hooks/useAddRow.ts
1645
1601
  import { useCallback } from "react";
1646
- import { Obj as Obj4 } from "@dxos/echo";
1647
- var useAddRow = ({ space, schema }) => {
1602
+ import { Obj as Obj3 } from "@dxos/echo";
1603
+ var useAddRow = ({ db, schema }) => {
1648
1604
  return useCallback((data) => {
1649
- if (space && schema) {
1605
+ if (db && schema) {
1650
1606
  try {
1651
- space.db.add(Obj4.make(schema, data ?? {}));
1607
+ db.add(Obj3.make(schema, data ?? {}));
1652
1608
  return "final";
1653
1609
  } catch (error) {
1654
1610
  return "draft";
@@ -1656,129 +1612,137 @@ var useAddRow = ({ space, schema }) => {
1656
1612
  }
1657
1613
  return "final";
1658
1614
  }, [
1659
- space,
1615
+ db,
1660
1616
  schema
1661
1617
  ]);
1662
1618
  };
1663
1619
 
1664
1620
  // 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
- ]);
1621
+ import { useState } from "react";
1622
+ import { Type } from "@dxos/echo";
1623
+ import { useAsyncEffect } from "@dxos/react-ui";
1624
+ import { ProjectionModel, createDirectChangeCallback as createDirectChangeCallback2, createEchoChangeCallback as createEchoChangeCallback2 } from "@dxos/schema";
1625
+ var useProjectionModel = (schema, table, registry) => {
1626
+ const [projection, setProjection] = useState();
1627
+ useAsyncEffect(async () => {
1628
+ if (schema && table) {
1629
+ const view = await table.view.load();
1630
+ const jsonSchema = Type.isMutable(schema) ? schema.jsonSchema : Type.toJsonSchema(schema);
1631
+ const change = Type.isMutable(schema) ? createEchoChangeCallback2(view, schema) : createDirectChangeCallback2(view.projection, jsonSchema);
1632
+ const projection2 = new ProjectionModel({
1633
+ registry,
1634
+ view,
1635
+ baseSchema: jsonSchema,
1636
+ change
1637
+ });
1638
+ projection2.normalizeView();
1639
+ setProjection(projection2);
1640
+ }
1641
+ }, [
1642
+ schema,
1643
+ table,
1644
+ registry
1645
+ ]);
1646
+ return projection;
1647
+ };
1678
1648
 
1679
1649
  // 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";
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";
1685
1653
  import { useSelected, useSelectionActions } from "@dxos/react-ui-attention";
1686
1654
  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);
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
1750
1679
  });
1751
- return () => {
1752
- clear();
1753
- unsubscribe();
1754
- };
1755
- }, [
1756
- model,
1757
- onSelectionChanged
1758
- ]);
1759
- return model;
1760
- } finally {
1761
- _effect.f();
1762
- }
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;
1763
1727
  };
1764
1728
 
1765
1729
  export {
1766
- getBaseSchema,
1767
- makeDynamicTable,
1768
- touch,
1730
+ extractOrder,
1731
+ getSortValue,
1732
+ compareValues,
1769
1733
  BUTTON_IDENTIFIERS,
1770
1734
  tableButtons,
1771
1735
  CONTROL_IDENTIFIERS,
1772
1736
  CheckboxStory,
1773
1737
  SwitchStory,
1774
1738
  tableControls,
1775
- narrowSchema,
1739
+ createEchoChangeCallback,
1740
+ createDirectChangeCallback,
1776
1741
  TableModel,
1777
- ModalController,
1778
1742
  TablePresentation,
1779
1743
  cellClassesForRowSelection,
1780
1744
  useAddRow,
1781
1745
  useProjectionModel,
1782
1746
  useTableModel
1783
1747
  };
1784
- //# sourceMappingURL=chunk-CWSGS3Y2.mjs.map
1748
+ //# sourceMappingURL=chunk-2ATAIIAO.mjs.map