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

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