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