@dxos/react-ui-table 0.8.4-main.e8ec1fe → 0.8.4-main.effb148878

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