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