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