@dxos/react-ui-table 0.8.4-main.7ace549 → 0.8.4-main.8360d9e660

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