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

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