@dxos/react-ui-table 0.8.4-main.ae835ea → 0.8.4-main.bc674ce

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