@dxos/react-ui-table 0.8.4-main.72ec0f3 → 0.8.4-main.74a063c4e0

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