@dxos/react-ui-table 0.8.4-main.7ace549 → 0.8.4-main.8baae0fced

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 (157) 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-KNC7XYT6.mjs → chunk-YDXPC7HA.mjs} +611 -699
  7. package/dist/lib/browser/chunk-YDXPC7HA.mjs.map +7 -0
  8. package/dist/lib/browser/index.mjs +1109 -1033
  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 +25 -22
  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 +2 -1
  16. package/dist/lib/node-esm/{chunk-CWSGS3Y2.mjs → chunk-2AN3QQ3D.mjs} +611 -698
  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 +1109 -1033
  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 +25 -22
  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 +2 -1
  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 +86 -83
  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 -13
  60. package/dist/types/src/components/testing.d.ts.map +1 -1
  61. package/dist/types/src/hooks/useAddRow.d.ts +5 -6
  62. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  63. package/dist/types/src/hooks/useProjectionModel.d.ts +2 -1
  64. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -1
  65. package/dist/types/src/hooks/useTableModel.d.ts +6 -5
  66. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  67. package/dist/types/src/index.d.ts +0 -2
  68. package/dist/types/src/index.d.ts.map +1 -1
  69. package/dist/types/src/model/modal-controller.d.ts +5 -1
  70. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  71. package/dist/types/src/model/selection-model.d.ts +18 -9
  72. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  73. package/dist/types/src/model/table-model.d.ts +99 -18
  74. package/dist/types/src/model/table-model.d.ts.map +1 -1
  75. package/dist/types/src/model/table-presentation.d.ts +3 -1
  76. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  77. package/dist/types/src/playwright/TableManager.d.ts +2 -1
  78. package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
  79. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -1
  80. package/dist/types/src/testing/useTestTableModel.d.ts +5 -8
  81. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  82. package/dist/types/src/translations.d.ts +21 -23
  83. package/dist/types/src/translations.d.ts.map +1 -1
  84. package/dist/types/src/types/Table.d.ts +12 -15
  85. package/dist/types/src/types/Table.d.ts.map +1 -1
  86. package/dist/types/src/util/dynamic-table.d.ts +12 -10
  87. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  88. package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
  89. package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
  90. package/dist/types/src/util/index.d.ts +2 -0
  91. package/dist/types/src/util/index.d.ts.map +1 -1
  92. package/dist/types/src/util/query-ast.d.ts +7 -0
  93. package/dist/types/src/util/query-ast.d.ts.map +1 -0
  94. package/dist/types/src/util/schema.d.ts.map +1 -1
  95. package/dist/types/src/util/signals.d.ts.map +1 -1
  96. package/dist/types/src/util/sort.d.ts +12 -0
  97. package/dist/types/src/util/sort.d.ts.map +1 -0
  98. package/dist/types/src/util/table-buttons.d.ts +20 -20
  99. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  100. package/dist/types/src/util/table-controls.d.ts +4 -4
  101. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  102. package/dist/types/src/util/tag.d.ts.map +1 -1
  103. package/dist/types/tsconfig.tsbuildinfo +1 -1
  104. package/package.json +67 -77
  105. package/src/components/Table/ColumnActionsMenu.tsx +17 -12
  106. package/src/components/Table/ColumnSettings.tsx +8 -9
  107. package/src/components/Table/DynamicTable.stories.tsx +25 -25
  108. package/src/components/Table/DynamicTable.tsx +31 -40
  109. package/src/components/Table/Relations.stories.tsx +96 -53
  110. package/src/components/Table/RowActionsMenu.tsx +6 -4
  111. package/src/components/Table/Table.stories.tsx +91 -81
  112. package/src/components/Table/Table.tsx +43 -407
  113. package/src/components/Table/TableContent.tsx +425 -0
  114. package/src/components/{TableToolbar → Table}/TableToolbar.tsx +44 -45
  115. package/src/components/TableCellEditor/CellValidationMessage.tsx +5 -6
  116. package/src/components/TableCellEditor/FormCellEditor.tsx +120 -136
  117. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +18 -16
  118. package/src/components/TableCellEditor/TableCellEditor.tsx +31 -29
  119. package/src/components/index.ts +0 -1
  120. package/src/components/testing.ts +42 -16
  121. package/src/hooks/useAddRow.ts +8 -10
  122. package/src/hooks/useProjectionModel.ts +29 -9
  123. package/src/hooks/useTableModel.ts +27 -23
  124. package/src/index.ts +0 -4
  125. package/src/model/modal-controller.ts +23 -12
  126. package/src/model/selection-model.ts +75 -44
  127. package/src/model/table-model.test.ts +81 -54
  128. package/src/model/table-model.ts +410 -139
  129. package/src/model/table-presentation.test.ts +58 -49
  130. package/src/model/table-presentation.ts +34 -30
  131. package/src/playwright/TableManager.ts +12 -5
  132. package/src/playwright/playwright.config.ts +1 -1
  133. package/src/playwright/smoke.spec.ts +51 -7
  134. package/src/testing/useTestTableModel.ts +26 -27
  135. package/src/translations.ts +20 -22
  136. package/src/types/Table.ts +28 -39
  137. package/src/util/dynamic-table.test.ts +70 -0
  138. package/src/util/dynamic-table.ts +35 -19
  139. package/src/util/index.ts +2 -0
  140. package/src/util/query-ast.ts +22 -0
  141. package/src/util/sort.ts +90 -0
  142. package/src/util/table-buttons.ts +1 -1
  143. package/src/util/table-controls.ts +2 -2
  144. package/dist/lib/browser/chunk-5XWSB3JP.mjs +0 -71
  145. package/dist/lib/browser/chunk-5XWSB3JP.mjs.map +0 -7
  146. package/dist/lib/browser/chunk-KNC7XYT6.mjs.map +0 -7
  147. package/dist/lib/node-esm/chunk-CWSGS3Y2.mjs.map +0 -7
  148. package/dist/lib/node-esm/chunk-MKTMOY46.mjs +0 -72
  149. package/dist/lib/node-esm/chunk-MKTMOY46.mjs.map +0 -7
  150. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +0 -21
  151. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +0 -1
  152. package/dist/types/src/components/TableToolbar/index.d.ts +0 -2
  153. package/dist/types/src/components/TableToolbar/index.d.ts.map +0 -1
  154. package/dist/types/src/model/table-sorting.d.ts +0 -56
  155. package/dist/types/src/model/table-sorting.d.ts.map +0 -1
  156. package/src/components/TableToolbar/index.ts +0 -5
  157. package/src/model/table-sorting.ts +0 -234
@@ -1,86 +1,67 @@
1
1
  import { createRequire } from 'node:module';const require = createRequire(import.meta.url);
2
- import {
3
- Table_exports
4
- } from "./chunk-MKTMOY46.mjs";
5
2
 
6
- // src/util/dynamic-table.ts
7
- import { Filter, Obj, Query, Ref, Type } from "@dxos/echo";
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: Type.getTypename(schema),
19
- jsonSchema: Type.toJsonSchema(schema)
20
- };
21
- } else if (typename && jsonSchema) {
22
- return {
23
- typename,
24
- jsonSchema
25
- };
26
- } else {
27
- throw new Error("invalid properties");
3
+ // src/util/query-ast.ts
4
+ var extractOrder = (queryAst) => {
5
+ if (queryAst.type === "order") {
6
+ return queryAst.order;
28
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;
29
15
  };
30
- var makeDynamicTable = ({ jsonSchema, properties }) => {
31
- const view = View.make({
32
- query: Query.select(Filter.everything()),
33
- jsonSchema,
34
- ...properties && {
35
- fields: properties.map((property) => property.name)
36
- }
37
- });
38
- const object = Obj.make(Table_exports.Table, {
39
- view: Ref.make(view),
40
- sizes: {}
41
- });
42
- const projection = new ProjectionModel(jsonSchema, view.projection);
43
- projection.normalizeView();
44
- if (properties && projection.fields) {
45
- setProperties(view, projection, object, properties);
46
- }
47
- return {
48
- projection,
49
- object
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
- }
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();
78
28
  }
29
+ return String(value);
30
+ }
31
+ if (format === Format.TypeFormat.Number || format === Format.TypeFormat.Integer || format === Format.TypeFormat.Currency || format === Format.TypeFormat.Percent || format === Format.TypeFormat.Duration) {
32
+ const num = typeof value === "number" ? value : Number(value);
33
+ if (!isNaN(num)) {
34
+ return num;
35
+ }
36
+ return String(value);
37
+ }
38
+ if (typeof value === "string") {
39
+ return value;
40
+ }
41
+ if (typeof value === "number") {
42
+ return value;
79
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;
80
64
  };
81
-
82
- // src/util/signals.ts
83
- var touch = (value) => value;
84
65
 
85
66
  // src/util/table-buttons.ts
86
67
  var BUTTON_IDENTIFIERS = {
@@ -93,7 +74,7 @@ var BUTTON_IDENTIFIERS = {
93
74
  };
94
75
  var createButton = ({ attr, data, disabled = false, icon, testId, type: _type = "primary" }) => {
95
76
  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>`;
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>`;
97
78
  };
98
79
  var addColumnButton = {
99
80
  attr: BUTTON_IDENTIFIERS.newColumn,
@@ -206,8 +187,8 @@ var CONTROL_IDENTIFIERS = {
206
187
  switch: "data-table-switch"
207
188
  };
208
189
  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"
190
+ checkbox: "absolute inset-y-[.375rem] end-[.375rem] dx-checkbox",
191
+ switch: "absolute inset-y-[.25rem] end-[.25rem] dx-checkbox--switch"
211
192
  };
212
193
  var renderAttributes = (data) => {
213
194
  return Object.entries(data).map(([k, v]) => `${k}="${v}"`).join(" ");
@@ -255,29 +236,15 @@ var tableControls = {
255
236
  }
256
237
  };
257
238
 
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
239
  // src/model/table-model.ts
274
- import { computed as computed3, effect as effect2, signal as signal3 } from "@preact/signals-core";
240
+ import { Atom as Atom2 } from "@effect-atom/atom-react";
275
241
  import { Resource as Resource2 } from "@dxos/context";
276
- import { Format as Format2, Obj as Obj2, Ref as Ref2 } from "@dxos/echo";
277
- import { 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";
278
246
  import { invariant } from "@dxos/invariant";
279
247
  import { ObjectId } from "@dxos/keys";
280
- import { getSnapshot, isLiveObject } from "@dxos/live-object";
281
248
  import { formatForEditing, parseValue } from "@dxos/react-ui-form";
282
249
  import { validateSchema } from "@dxos/schema";
283
250
 
@@ -298,75 +265,99 @@ var extractTagIds = (value) => {
298
265
  };
299
266
 
300
267
  // src/model/selection-model.ts
301
- import { computed, effect, signal } from "@preact/signals-core";
268
+ import { Atom } from "@effect-atom/atom-react";
302
269
  import { Resource } from "@dxos/context";
303
270
  var SelectionModel = class extends Resource {
271
+ _registry;
304
272
  _rows;
273
+ _selection;
274
+ _validSelectedIds;
275
+ _hasSelection;
276
+ _allSelected;
305
277
  _selectionMode;
306
- _initialSelection;
307
278
  _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));
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
+ });
330
306
  }
331
307
  async _open() {
332
- const selectionWatcher = effect(() => {
333
- touch(this._selection.value);
308
+ const selectionUnsubscribe = this._registry.subscribe(this._selection, () => {
334
309
  this._onSelectionChanged?.();
335
310
  });
336
- this._ctx.onDispose(selectionWatcher);
311
+ this._ctx.onDispose(selectionUnsubscribe);
337
312
  }
338
313
  //
339
314
  // Getters
340
315
  //
341
- get selection() {
316
+ /** Get the selection atom for reactive access. */
317
+ get selectionAtom() {
342
318
  return this._selection;
343
319
  }
344
- 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() {
345
326
  return this._hasSelection;
346
327
  }
347
- 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() {
348
334
  return this._allSelected;
349
335
  }
336
+ /** Check if all rows are selected. */
337
+ get allRowsSelected() {
338
+ return this._registry.get(this._allSelected);
339
+ }
350
340
  get selectionMode() {
351
341
  return this._selectionMode;
352
342
  }
353
343
  getSelectedRows = () => {
354
- const selectedIds = this._validSelectedIds.value;
355
- 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));
356
346
  };
357
347
  isRowIndexSelected = (rowIndex) => {
358
- const row = this._rows.value[rowIndex];
359
- 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);
360
350
  };
361
351
  isObjectSelected = (object) => {
362
- return this._selection.value.has(object.id);
352
+ return this._registry.get(this._selection).has(object.id);
363
353
  };
364
354
  //
365
355
  // Manipulation
366
356
  //
367
357
  toggleSelectionForRowIndex = (rowIndex) => {
368
- const row = this._rows.value[rowIndex];
369
- 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);
370
361
  if (newSelection.has(row.id)) {
371
362
  newSelection.delete(row.id);
372
363
  } else {
@@ -375,195 +366,38 @@ var SelectionModel = class extends Resource {
375
366
  }
376
367
  newSelection.add(row.id);
377
368
  }
378
- this._selection.value = newSelection;
369
+ this._registry.set(this._selection, newSelection);
379
370
  };
380
371
  setSelection = (mode) => {
381
372
  switch (mode) {
382
373
  case "all": {
383
- 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)));
384
375
  break;
385
376
  }
386
377
  case "none": {
387
- this._selection.value = /* @__PURE__ */ new Set();
378
+ this._registry.set(this._selection, /* @__PURE__ */ new Set());
388
379
  break;
389
380
  }
390
381
  }
391
382
  };
392
383
  };
393
384
 
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 { Format, TypeEnum, getValue } from "@dxos/echo/internal";
398
- import { formatForDisplay } from "@dxos/react-ui-form";
399
- var TableSorting = class {
400
- _displayToDataIndex = /* @__PURE__ */ new Map();
401
- _rows;
402
- _getView;
403
- _projection;
404
- _localSort = signal2(void 0);
405
- _isDirty;
406
- sortedRows;
407
- constructor({ rows, projection, getView }) {
408
- this._rows = rows;
409
- this._getView = getView;
410
- this._projection = projection;
411
- this._isDirty = computed2(() => {
412
- const local = this._localSort.value;
413
- const viewSort = this._getView().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._getView().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
- const view = this._getView();
483
- if (this._localSort.value.type === "active") {
484
- view.sort = [
485
- this._localSort.value.sort
486
- ];
487
- } else {
488
- view.sort = [];
489
- }
490
- this._localSort.value = void 0;
491
- }
492
- }
493
- //
494
- // Initialisation.
495
- //
496
- initialiseSortedRows() {
497
- return computed2(() => {
498
- this._displayToDataIndex.clear();
499
- const sort = this.sorting;
500
- if (!sort) {
501
- return this._rows.value;
502
- }
503
- const field = this._projection.fields.find((f) => f.id === sort.fieldId);
504
- if (!field) {
505
- return this._rows.value;
506
- }
507
- const { props } = this._projection.getFieldProjection(field.id);
508
- const dataWithIndices = this._rows.value.map((item, index) => {
509
- const sortValue = this.getSortValue(props, field, item);
510
- const isEmpty = !(props.type === TypeEnum.Boolean) && (sortValue == null || sortValue === "");
511
- return {
512
- item,
513
- index,
514
- value: sortValue,
515
- isEmpty
516
- };
517
- });
518
- const ordered = orderBy(dataWithIndices, [
519
- "isEmpty",
520
- "value"
521
- ], [
522
- "asc",
523
- sort.direction
524
- ]);
525
- ordered.forEach(({ index: dataIndex }, displayIndex) => {
526
- if (displayIndex !== dataIndex) {
527
- this._displayToDataIndex.set(displayIndex, dataIndex);
528
- }
529
- });
530
- return ordered.map(({ item }) => item);
531
- });
532
- }
533
- getSortValue(props, field, item) {
534
- const { type, format } = props;
535
- const value = getValue(item, field.path);
536
- if (type === TypeEnum.Boolean) {
537
- return !!value;
538
- }
539
- if (type === TypeEnum.Number) {
540
- return value;
541
- }
542
- if (value == null) {
543
- return value;
544
- }
545
- if (format === Format.TypeFormat.SingleSelect) {
546
- const options = props.options;
547
- if (!options) {
548
- return void 0;
549
- }
550
- const option = options.find((option2) => option2.id === value);
551
- return option ? options.indexOf(option) : void 0;
552
- }
553
- if (format === Format.TypeFormat.Ref && field.referencePath) {
554
- const refValue = getValue(value.target, field.referencePath);
555
- return refValue;
556
- }
557
- return formatForDisplay({
558
- type,
559
- format,
560
- value
561
- });
562
- }
563
- };
564
-
565
385
  // src/model/table-model.ts
566
386
  var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-table/src/model/table-model.ts";
387
+ var createEchoChangeCallback = (table) => ({
388
+ table: (mutate) => Obj.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
+ });
567
401
  var defaultFeatures = {
568
402
  selection: {
569
403
  enabled: true,
@@ -573,86 +407,136 @@ var defaultFeatures = {
573
407
  schemaEditable: false
574
408
  };
575
409
  var TableModel = class extends Resource2 {
410
+ _registry;
576
411
  _object;
577
412
  _projection;
578
- _visibleRange = signal3({
579
- start: {
580
- row: 0,
581
- col: 0
582
- },
583
- end: {
584
- row: 0,
585
- col: 0
586
- }
587
- });
413
+ _db;
414
+ _change;
415
+ _visibleRange;
588
416
  _onInsertRow;
589
417
  _onDeleteRows;
590
- _onDeleteColumn;
418
+ _onColumnDelete;
591
419
  _onCellUpdate;
592
420
  _onRowOrderChange;
593
421
  _onRowAction;
594
422
  _rowActions;
595
423
  _features;
596
- _rows = signal3([]);
597
- _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).
598
431
  _sorting;
432
+ // Derived atom for sorted rows (used by rows getter and SelectionModel).
433
+ _sortedRows;
434
+ // Derived atom for viewDirty state.
435
+ _viewDirty;
599
436
  _pinnedRows;
600
437
  _selection;
601
438
  _columnMeta;
602
- constructor({ object, 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 = {
603
442
  top: [],
604
443
  bottom: []
605
- }, rowActions = [], onCellUpdate, onDeleteColumn, onDeleteRows, onInsertRow, onRowOrderChange, onRowAction }) {
444
+ }, rowActions = [], onCellUpdate, onColumnDelete, onDeleteRows, onInsertRow, onRowOrderChange, onRowAction }) {
606
445
  super();
446
+ this._registry = registry;
607
447
  this._object = object;
608
448
  this._projection = projection;
609
449
  this._projection.normalizeView();
450
+ this._db = db;
451
+ this._change = change ?? createEchoChangeCallback(object);
610
452
  this._features = {
611
453
  ...defaultFeatures,
612
454
  ...features
613
455
  };
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: 137,
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
- ]
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;
622
488
  });
623
- this._sorting = new TableSorting({
624
- rows: this._rows,
625
- getView: () => this.view,
626
- projection: this._projection
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;
627
524
  });
628
- if (sorting.length > 0) {
629
- const [sort] = sorting;
630
- this._sorting.setSort(sort.fieldId, sort.direction);
631
- }
632
- this._selection = new SelectionModel(this._sorting.sortedRows, this._features.selection.mode ?? "multiple", initialSelection, () => this._onRowOrderChange?.());
633
525
  this._pinnedRows = pinnedRows;
634
526
  this._rowActions = rowActions;
635
527
  this._onInsertRow = onInsertRow;
636
528
  this._onDeleteRows = onDeleteRows;
637
- this._onDeleteColumn = onDeleteColumn;
529
+ this._onColumnDelete = onColumnDelete;
638
530
  this._onCellUpdate = onCellUpdate;
639
531
  this._onRowOrderChange = onRowOrderChange;
640
532
  this._onRowAction = onRowAction;
641
533
  }
642
534
  get id() {
643
- return Obj2.getDXN(this._object).toString();
535
+ return Obj.getDXN(this._object).toString();
644
536
  }
645
537
  get view() {
646
538
  const view = this._object.view.target;
647
- invariant(view, "Table model not initialized", {
648
- F: __dxlog_file,
649
- L: 176,
650
- S: this,
651
- A: [
652
- "view",
653
- "'Table model not initialized'"
654
- ]
655
- });
539
+ invariant(view, "Table model not initialized", { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 186, S: this, A: ["view", "'Table model not initialized'"] });
656
540
  return view;
657
541
  }
658
542
  get table() {
@@ -661,49 +545,63 @@ var TableModel = class extends Resource2 {
661
545
  get projection() {
662
546
  return this._projection;
663
547
  }
548
+ get db() {
549
+ return this._db;
550
+ }
551
+ /**
552
+ * Atom for reactive access to rows with in-memory sorting applied.
553
+ */
664
554
  get rows() {
665
- 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);
666
574
  }
667
575
  get features() {
668
576
  return this._features;
669
577
  }
670
578
  /**
671
- * @reactive
579
+ * Gets the current sort state.
580
+ * Priority: in-memory sort (local changes) > persisted sort (from query AST).
672
581
  */
673
- get sorting() {
674
- return this._sorting;
582
+ getSorting() {
583
+ return this._registry.get(this._sorting);
675
584
  }
676
585
  /**
677
586
  * Gets the row data at the specified display index.
678
587
  */
679
588
  getRowAt(displayIndex) {
680
- if (displayIndex < 0 || displayIndex >= this.sorting.sortedRows.value.length) {
589
+ const sortedRows = this._registry.get(this._sortedRows);
590
+ if (displayIndex < 0 || displayIndex >= sortedRows.length) {
681
591
  return void 0;
682
592
  }
683
- return this.sorting.sortedRows.value[displayIndex];
593
+ return sortedRows[displayIndex];
684
594
  }
685
595
  get pinnedRows() {
686
596
  return this._pinnedRows;
687
597
  }
688
598
  get columnMeta() {
689
- invariant(this._columnMeta, void 0, {
690
- F: __dxlog_file,
691
- L: 219,
692
- S: this,
693
- A: [
694
- "this._columnMeta",
695
- ""
696
- ]
697
- });
599
+ invariant(this._columnMeta, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 240, S: this, A: ["this._columnMeta", ""] });
698
600
  return this._columnMeta;
699
601
  }
700
602
  get selection() {
701
603
  return this._selection;
702
604
  }
703
- /** @reactive */
704
- get isViewDirty() {
705
- return this.sorting.isDirty;
706
- }
707
605
  get rowActions() {
708
606
  return this._rowActions;
709
607
  }
@@ -717,8 +615,9 @@ var TableModel = class extends Resource2 {
717
615
  await this._selection.open(this._ctx);
718
616
  }
719
617
  initializeColumnMeta() {
720
- this._columnMeta = computed3(() => {
721
- 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);
722
621
  const meta = Object.fromEntries(fields.map((field, index) => [
723
622
  index,
724
623
  {
@@ -744,49 +643,62 @@ var TableModel = class extends Resource2 {
744
643
  });
745
644
  }
746
645
  initializeEffects() {
747
- const rowOrderWatcher = effect2(() => {
748
- touch(this.sorting.sortedRows.value);
646
+ const rowsUnsubscribe = this._registry.subscribe(this._rows, () => {
749
647
  this._onRowOrderChange?.();
750
648
  });
751
- this._ctx.onDispose(rowOrderWatcher);
752
- const rowEffectManager = effect2(() => {
753
- const { start, end } = touch(this._visibleRange.value);
754
- const rowEffects = [];
755
- for (let row = start.row; row <= end.row; row++) {
756
- rowEffects.push(effect2(() => {
757
- const obj = this.sorting.sortedRows.value[row];
758
- this._projection?.fields.forEach((field) => touch(getValue2(obj, field.path)));
759
- this._onCellUpdate?.({
760
- row,
761
- col: start.col,
762
- 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
+ });
763
668
  });
764
- }));
669
+ rowSubscriptions.set(obj.id, unsub);
670
+ }
671
+ }
672
+ });
673
+ this._ctx.onDispose(() => {
674
+ visibleRangeUnsubscribe();
675
+ for (const unsub of rowSubscriptions.values()) {
676
+ unsub();
765
677
  }
766
- return () => rowEffects.forEach((cleanup) => cleanup());
767
678
  });
768
- this._ctx.onDispose(rowEffectManager);
769
- const draftRowsWatcher = effect2(() => {
770
- touch(this._draftRows.value);
679
+ const draftRowsUnsubscribe = this._registry.subscribe(this._draftRows, () => {
771
680
  this._onRowOrderChange?.();
772
681
  });
773
- this._ctx.onDispose(draftRowsWatcher);
682
+ this._ctx.onDispose(draftRowsUnsubscribe);
774
683
  }
775
684
  //
776
685
  // Data
777
686
  //
778
687
  setRows = (obj) => {
779
- this._rows.value = obj;
688
+ this._registry.set(this._rows, obj);
780
689
  };
781
- getRowCount = () => this._rows.value.length;
782
- /**
783
- * @reactive
784
- */
785
- getDraftRowCount = () => this._draftRows.value.length;
786
- // 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. */
787
695
  get draftRows() {
788
696
  return this._draftRows;
789
697
  }
698
+ /** Gets the current draft rows. */
699
+ getDraftRows() {
700
+ return this._registry.get(this._draftRows);
701
+ }
790
702
  /**
791
703
  * Checks if a specific field path has validation errors for a given draft row.
792
704
  * @param draftRowIndex - The index of the draft row to check
@@ -794,17 +706,17 @@ var TableModel = class extends Resource2 {
794
706
  * @returns true if the field has validation errors, false otherwise
795
707
  */
796
708
  hasDraftRowValidationError(draftRowIndex, fieldPath) {
797
- if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.value.length) {
709
+ if (draftRowIndex < 0 || draftRowIndex >= this._registry.get(this._draftRows).length) {
798
710
  return false;
799
711
  }
800
- const draftRow = this._draftRows.value[draftRowIndex];
712
+ const draftRow = this._registry.get(this._draftRows)[draftRowIndex];
801
713
  const validationErrors = draftRow.validationErrors || [];
802
714
  return validationErrors.some((error) => error.path === fieldPath);
803
715
  }
804
- getColumnCount = () => this._projection?.fields.length ?? 0;
716
+ getColumnCount = () => this._projection?.getFields().length ?? 0;
805
717
  insertRow = () => {
806
718
  const result = this._onInsertRow?.();
807
- if (result === "draft" && this._draftRows.value.length === 0) {
719
+ if (result === "draft" && this._registry.get(this._draftRows).length === 0) {
808
720
  this.createDraftRow();
809
721
  }
810
722
  return result ?? "final";
@@ -822,16 +734,16 @@ var TableModel = class extends Resource2 {
822
734
  valid: validationErrors.length === 0,
823
735
  validationErrors
824
736
  };
825
- this._draftRows.value = [
826
- ...this._draftRows.value,
737
+ this._registry.set(this._draftRows, [
738
+ ...this._registry.get(this._draftRows),
827
739
  draftRow
828
- ];
740
+ ]);
829
741
  }
830
742
  validateDraftRow(draftRowIndex) {
831
- if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.value.length) {
743
+ if (draftRowIndex < 0 || draftRowIndex >= this._registry.get(this._draftRows).length) {
832
744
  return;
833
745
  }
834
- const draftRow = this._draftRows.value[draftRowIndex];
746
+ const draftRow = this._registry.get(this._draftRows)[draftRowIndex];
835
747
  const validationErrors = this.validateDraftRowData(draftRow.data);
836
748
  const updatedDraftRow = {
837
749
  ...draftRow,
@@ -839,69 +751,71 @@ var TableModel = class extends Resource2 {
839
751
  validationErrors
840
752
  };
841
753
  const newDraftRows = [
842
- ...this._draftRows.value
754
+ ...this._registry.get(this._draftRows)
843
755
  ];
844
756
  newDraftRows[draftRowIndex] = updatedDraftRow;
845
- this._draftRows.value = newDraftRows;
757
+ this._registry.set(this._draftRows, newDraftRows);
846
758
  }
847
759
  validateDraftRowData(data) {
848
760
  const schema = toEffectSchema(this._projection.baseSchema);
849
761
  return validateSchema(schema, data) || [];
850
762
  }
851
763
  commitDraftRow = (draftRowIndex) => {
852
- if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.value.length) {
764
+ if (draftRowIndex < 0 || draftRowIndex >= this._registry.get(this._draftRows).length) {
853
765
  return false;
854
766
  }
855
- const draftRow = this._draftRows.value[draftRowIndex];
767
+ const draftRow = this._registry.get(this._draftRows)[draftRowIndex];
856
768
  if (!draftRow.valid) {
857
769
  return false;
858
770
  }
859
771
  const insertRowResult = this._onInsertRow?.(draftRow.data);
860
772
  if (insertRowResult === "final") {
861
- const newDraftRows = this._draftRows.value.filter((_, index) => index !== draftRowIndex);
862
- this._draftRows.value = newDraftRows;
773
+ const newDraftRows = this._registry.get(this._draftRows).filter((_, index) => index !== draftRowIndex);
774
+ this._registry.set(this._draftRows, newDraftRows);
863
775
  }
864
776
  return insertRowResult === "final";
865
777
  };
866
778
  deleteRow = (rowIndex) => {
867
- const row = this._sorting.getDataIndex(rowIndex);
868
- const obj = this._rows.value[row];
779
+ const sortedRows = this._registry.get(this._sortedRows);
780
+ const obj = sortedRows[rowIndex];
869
781
  const objectsToDelete = [];
870
- if (this.features.selection.enabled && this._selection.hasSelection.value) {
782
+ if (this.features.selection.enabled && this._selection.hasSelection) {
871
783
  const selectedRows = this._selection.getSelectedRows();
872
784
  objectsToDelete.push(...selectedRows);
873
- } else {
785
+ } else if (obj) {
874
786
  objectsToDelete.push(obj);
875
787
  }
876
- this._onDeleteRows?.(row, objectsToDelete);
788
+ if (objectsToDelete.length > 0) {
789
+ this._onDeleteRows?.(rowIndex, objectsToDelete);
790
+ }
877
791
  };
878
792
  handleRowAction = (actionId, rowIndex) => {
879
793
  if (!this._onRowAction) {
880
794
  return;
881
795
  }
882
- const row = this._sorting.getDataIndex(rowIndex);
883
- const data = this._rows.value[row];
796
+ const sortedRows = this._registry.get(this._sortedRows);
797
+ const data = sortedRows[rowIndex];
884
798
  if (data) {
885
799
  this._onRowAction(actionId, data);
886
800
  }
887
801
  };
888
802
  getCellData = (cell) => {
889
803
  const { col, row, plane } = cell;
890
- const fields = this._projection?.fields ?? [];
804
+ const fields = this._projection?.getFields() ?? [];
891
805
  if (col < 0 || col >= fields.length) {
892
806
  return void 0;
893
807
  }
894
808
  const field = fields[col];
895
809
  let value;
896
810
  if (plane === "frozenRowsEnd") {
897
- if (row >= 0 && row < this._draftRows.value.length) {
898
- 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);
899
813
  } else {
900
814
  return void 0;
901
815
  }
902
816
  } else {
903
- const dataIndex = this._sorting.getDataIndex(row);
904
- value = getValue2(this._rows.value[dataIndex], field.path);
817
+ const sortedRows = this._registry.get(this._sortedRows);
818
+ value = getValue(sortedRows[row], field.path);
905
819
  }
906
820
  if (value == null) {
907
821
  return "";
@@ -912,7 +826,7 @@ var TableModel = class extends Resource2 {
912
826
  if (!field.referencePath) {
913
827
  return "";
914
828
  }
915
- return getValue2(value.target, field.referencePath);
829
+ return getValue(value.target, field.referencePath);
916
830
  }
917
831
  default: {
918
832
  return formatForEditing({
@@ -924,7 +838,7 @@ var TableModel = class extends Resource2 {
924
838
  }
925
839
  };
926
840
  validateCellData = async ({ col, row, plane }, value) => {
927
- const fields = this._projection?.fields ?? [];
841
+ const fields = this._projection?.getFields() ?? [];
928
842
  if (col < 0 || col >= fields.length) {
929
843
  return {
930
844
  valid: false,
@@ -936,14 +850,14 @@ var TableModel = class extends Resource2 {
936
850
  const transformedValue = editorTextToCellValue(props, value);
937
851
  const isDraftRow = plane === "frozenRowsEnd";
938
852
  if (isDraftRow) {
939
- const isInvalidDraftRowIndex = row < 0 || row >= this._draftRows.value.length;
853
+ const isInvalidDraftRowIndex = row < 0 || row >= this._registry.get(this._draftRows).length;
940
854
  if (isInvalidDraftRowIndex) {
941
855
  return {
942
856
  valid: false,
943
857
  error: "Invalid draft row index"
944
858
  };
945
859
  }
946
- const draftRow = this._draftRows.value[row];
860
+ const draftRow = this._registry.get(this._draftRows)[row];
947
861
  const snapshot = {
948
862
  ...draftRow.data
949
863
  };
@@ -962,41 +876,18 @@ var TableModel = class extends Resource2 {
962
876
  valid: true
963
877
  };
964
878
  } else {
965
- const rowIdx = this._sorting.getDataIndex(row);
966
- const currentRow = this._rows.value[rowIdx];
967
- invariant(currentRow, "Invalid row index", {
968
- F: __dxlog_file,
969
- L: 516,
970
- S: this,
971
- A: [
972
- "currentRow",
973
- "'Invalid row index'"
974
- ]
975
- });
976
- 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
+ };
977
884
  setValue(snapshot, field.path, transformedValue);
978
- const schema = Obj2.getSchema(currentRow);
979
- invariant(schema, void 0, {
980
- F: __dxlog_file,
981
- L: 522,
982
- S: this,
983
- A: [
984
- "schema",
985
- ""
986
- ]
987
- });
885
+ const schema = Obj.getSchema(currentRow);
886
+ invariant(schema, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 539, S: this, A: ["schema", ""] });
988
887
  const validationResult = validateSchema(schema, snapshot);
989
888
  if (validationResult && validationResult.length > 0) {
990
889
  const error = validationResult[0];
991
- invariant(error.path === field.path, void 0, {
992
- F: __dxlog_file,
993
- L: 527,
994
- S: this,
995
- A: [
996
- "error.path === field.path",
997
- ""
998
- ]
999
- });
890
+ invariant(error.path === field.path, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 543, S: this, A: ["error.path === field.path", ""] });
1000
891
  return {
1001
892
  valid: false,
1002
893
  error: error.message
@@ -1009,24 +900,27 @@ var TableModel = class extends Resource2 {
1009
900
  };
1010
901
  setCellData = (cell, value) => {
1011
902
  const { col, row, plane } = cell;
1012
- const fields = this._projection?.fields ?? [];
903
+ const fields = this._projection?.getFields() ?? [];
1013
904
  if (col < 0 || col >= fields.length) {
1014
905
  return;
1015
906
  }
1016
907
  const field = fields[col];
1017
908
  const { props } = this._projection.getFieldProjection(field.id);
1018
909
  const transformedValue = editorTextToCellValue(props, value);
1019
- if (props.format === Format2.TypeFormat.Ref && !isLiveObject(value)) {
910
+ if (props.format === Format2.TypeFormat.Ref && !Obj.isObject(value)) {
1020
911
  return;
1021
912
  }
1022
913
  if (plane === "frozenRowsEnd") {
1023
- if (row >= 0 && row < this._draftRows.value.length) {
1024
- 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);
1025
916
  this.validateDraftRow(row);
1026
917
  }
1027
918
  } else {
1028
- const rowIdx = this._sorting.getDataIndex(row);
1029
- 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
+ });
1030
924
  }
1031
925
  };
1032
926
  /**
@@ -1035,12 +929,15 @@ var TableModel = class extends Resource2 {
1035
929
  * @param {(value: any) => any} update - A function that takes the current value and returns the updated value.
1036
930
  */
1037
931
  updateCellData({ col, row }, update) {
1038
- const dataRow = this._sorting.getDataIndex(row);
1039
- const fields = this._projection?.fields ?? [];
932
+ const fields = this._projection?.getFields() ?? [];
1040
933
  const field = fields[col];
1041
- 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);
1042
937
  const updatedValue = update(value);
1043
- setValue(this._rows.value[dataRow], field.path, updatedValue);
938
+ this._change.row(rowData, (mutableRow) => {
939
+ setValue(mutableRow, field.path, updatedValue);
940
+ });
1044
941
  }
1045
942
  /**
1046
943
  * Gets the domain-appropriate cell type for a given cell position.
@@ -1074,21 +971,23 @@ var TableModel = class extends Resource2 {
1074
971
  if (!this._projection) {
1075
972
  return;
1076
973
  }
1077
- const field = this._projection?.fields.find((field2) => field2.id === fieldId);
1078
- if (field && this._onDeleteColumn) {
1079
- 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);
1080
977
  }
1081
978
  }
1082
979
  //
1083
980
  // Resize
1084
981
  //
1085
982
  setColumnWidth(columnIndex, width) {
1086
- const fields = this._projection?.fields ?? [];
983
+ const fields = this._projection?.getFields() ?? [];
1087
984
  if (columnIndex < fields.length) {
1088
985
  const newWidth = Math.max(0, width);
1089
986
  const field = fields[columnIndex];
1090
987
  if (field) {
1091
- this.table.sizes[field.path] = newWidth;
988
+ this._change.table((mutableTable) => {
989
+ mutableTable.sizes[field.path] = newWidth;
990
+ });
1092
991
  }
1093
992
  }
1094
993
  }
@@ -1104,17 +1003,87 @@ var TableModel = class extends Resource2 {
1104
1003
  this._pinnedRows.bottom = this._pinnedRows.bottom.filter((index) => index !== rowIndex);
1105
1004
  }
1106
1005
  //
1107
- // View operations
1006
+ // Sort operations
1108
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
+ */
1109
1044
  saveView() {
1110
- 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);
1111
1080
  }
1112
1081
  };
1113
1082
  var editorTextToCellValue = (props, value) => {
1114
1083
  switch (props.format) {
1115
1084
  case Format2.TypeFormat.Ref: {
1116
- if (isLiveObject(value)) {
1117
- return Ref2.make(value);
1085
+ if (Obj.isObject(value)) {
1086
+ return Ref.make(value);
1118
1087
  } else {
1119
1088
  return value;
1120
1089
  }
@@ -1141,104 +1110,40 @@ var editorTextToCellValue = (props, value) => {
1141
1110
  }
1142
1111
  };
1143
1112
 
1144
- // src/model/modal-controller.ts
1145
- import { signal as signal4 } from "@preact/signals-core";
1146
- var ModalController = class {
1147
- _state = signal4({
1148
- type: "closed"
1149
- });
1150
- _triggerRef = {
1151
- current: null
1152
- };
1153
- get state() {
1154
- return this._state;
1155
- }
1156
- get trigger() {
1157
- return this._triggerRef;
1158
- }
1159
- setTrigger = (trigger) => {
1160
- this._triggerRef.current = trigger;
1161
- };
1162
- showRowMenu = (rowIndex) => {
1163
- this._state.value = {
1164
- type: "row",
1165
- rowIndex
1166
- };
1167
- };
1168
- showColumnMenu = (fieldId) => {
1169
- this._state.value = {
1170
- type: "column",
1171
- fieldId
1172
- };
1173
- };
1174
- showColumnCreator = () => {
1175
- this._state.value = {
1176
- type: "columnSettings",
1177
- mode: {
1178
- type: "create"
1179
- }
1180
- };
1181
- };
1182
- showReferencePanel = (targetId, schemaId) => {
1183
- this._state.value = {
1184
- type: "refPanel",
1185
- targetId,
1186
- typename: schemaId
1187
- };
1188
- };
1189
- openColumnSettings = () => {
1190
- if (this._state.value.type === "column") {
1191
- const fieldId = this._state.value.fieldId;
1192
- requestAnimationFrame(() => {
1193
- this._state.value = {
1194
- type: "columnSettings",
1195
- mode: {
1196
- type: "edit",
1197
- fieldId
1198
- }
1199
- };
1200
- });
1201
- }
1202
- };
1203
- close = () => {
1204
- this._state.value = {
1205
- type: "closed"
1206
- };
1207
- };
1208
- };
1209
-
1210
1113
  // src/model/table-presentation.ts
1211
- import { signal as signal5 } from "@preact/signals-core";
1114
+ import { Atom as Atom3 } from "@effect-atom/atom-react";
1212
1115
  import * as Predicate from "effect/Predicate";
1213
- import { Obj as Obj3 } from "@dxos/echo";
1214
- import { Format as Format3, TypeEnum as TypeEnum2, getValue as getValue3 } from "@dxos/echo/internal";
1215
- 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";
1216
1119
  import { toPlaneCellIndex } from "@dxos/react-ui-grid";
1217
- import { mx } from "@dxos/react-ui-theme";
1218
1120
  import { VIEW_FIELD_LIMIT } from "@dxos/schema";
1121
+ import { mx } from "@dxos/ui-theme";
1219
1122
  var TablePresentation = class {
1220
1123
  model;
1124
+ _registry;
1221
1125
  _visibleRange;
1222
1126
  fieldProjectionCache = /* @__PURE__ */ new Map();
1223
- constructor(model, _visibleRange = signal5({
1224
- start: {
1225
- row: 0,
1226
- col: 0
1227
- },
1228
- end: {
1229
- row: 0,
1230
- col: 0
1231
- }
1232
- })) {
1127
+ constructor(registry, model) {
1233
1128
  this.model = model;
1234
- 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
+ });
1235
1140
  }
1236
1141
  getCells(range, plane) {
1237
1142
  this.fieldProjectionCache.clear();
1238
1143
  let cells;
1239
1144
  switch (plane) {
1240
1145
  case "grid":
1241
- this._visibleRange.value = range;
1146
+ this._registry.set(this._visibleRange, range);
1242
1147
  cells = this.getMainGridCells(range);
1243
1148
  break;
1244
1149
  case "frozenRowsStart":
@@ -1290,7 +1195,7 @@ var TablePresentation = class {
1290
1195
  const { props } = this.getFieldProjection(field.id);
1291
1196
  const cell = {
1292
1197
  get value() {
1293
- const value = getValue3(obj, field.path);
1198
+ const value = getValue2(obj, field.path);
1294
1199
  if (value == null) {
1295
1200
  return "";
1296
1201
  }
@@ -1307,10 +1212,10 @@ var TablePresentation = class {
1307
1212
  if (!field.referencePath) {
1308
1213
  return "";
1309
1214
  }
1310
- return getValue3(value.target, field.referencePath);
1215
+ return getValue2(value.target, field.referencePath);
1311
1216
  }
1312
1217
  default: {
1313
- return formatForDisplay2({
1218
+ return formatForDisplay({
1314
1219
  type: props.type,
1315
1220
  format: props.format,
1316
1221
  value
@@ -1329,8 +1234,8 @@ var TablePresentation = class {
1329
1234
  classes.push(rowSelectionClasses);
1330
1235
  }
1331
1236
  cell.className = mx(classes);
1332
- if (props.type === TypeEnum2.Array) {
1333
- const targetArray = getValue3(obj, field.path);
1237
+ if (props.type === TypeEnum.Array) {
1238
+ const targetArray = getValue2(obj, field.path);
1334
1239
  if (targetArray && Array.isArray(targetArray)) {
1335
1240
  const getLabel = (value) => {
1336
1241
  if (!value) {
@@ -1357,14 +1262,14 @@ var TablePresentation = class {
1357
1262
  }
1358
1263
  }
1359
1264
  if (props.format === Format3.TypeFormat.Ref && props.referenceSchema) {
1360
- const targetObj = getValue3(obj, field.path)?.target;
1265
+ const targetObj = getValue2(obj, field.path)?.target;
1361
1266
  if (targetObj) {
1362
- const dxn = Obj3.getDXN(targetObj)?.toString();
1363
- 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>`;
1364
1269
  }
1365
1270
  }
1366
1271
  if (props.format === Format3.TypeFormat.Boolean) {
1367
- const value = getValue3(obj, field.path);
1272
+ const value = getValue2(obj, field.path);
1368
1273
  cell.accessoryHtml = tableControls.switch.render({
1369
1274
  colIndex,
1370
1275
  rowIndex: displayIndex,
@@ -1373,7 +1278,7 @@ var TablePresentation = class {
1373
1278
  cell.readonly = "no-text-select";
1374
1279
  }
1375
1280
  if (props.format === Format3.TypeFormat.SingleSelect) {
1376
- const value = getValue3(obj, field.path);
1281
+ const value = getValue2(obj, field.path);
1377
1282
  const options = this.model.projection.getFieldProjection(field.id).props.options;
1378
1283
  if (options) {
1379
1284
  const option = options.find((o) => o.id === value);
@@ -1383,7 +1288,7 @@ var TablePresentation = class {
1383
1288
  }
1384
1289
  }
1385
1290
  if (props.format === Format3.TypeFormat.MultiSelect) {
1386
- const values = getValue3(obj, field.path);
1291
+ const values = getValue2(obj, field.path);
1387
1292
  const options = this.model.projection.getFieldProjection(field.id).props.options;
1388
1293
  if (options && values && values.length > 0) {
1389
1294
  const tags = values.map((value) => {
@@ -1406,22 +1311,22 @@ var TablePresentation = class {
1406
1311
  }
1407
1312
  getMainGridCells(range) {
1408
1313
  const cells = {};
1409
- const fields = this.model.projection?.fields ?? [];
1314
+ const fields = this.model.projection?.getFields() ?? [];
1410
1315
  for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
1411
1316
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1412
1317
  const field = fields[col];
1413
1318
  if (!field) {
1414
1319
  continue;
1415
1320
  }
1416
- this.createDataCell(cells, this.model.rows.value[row], field, col, row);
1321
+ this.createDataCell(cells, this.model.getRows()[row], field, col, row);
1417
1322
  }
1418
1323
  }
1419
1324
  return cells;
1420
1325
  }
1421
1326
  getDraftRowCells(range) {
1422
1327
  const cells = {};
1423
- const fields = this.model.projection?.fields ?? [];
1424
- const draftRows = this.model.draftRows.value;
1328
+ const fields = this.model.projection?.getFields() ?? [];
1329
+ const draftRows = this.model.getDraftRows();
1425
1330
  if (draftRows.length === 0) {
1426
1331
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1427
1332
  cells[toPlaneCellIndex({
@@ -1461,11 +1366,11 @@ var TablePresentation = class {
1461
1366
  }
1462
1367
  getHeaderCells(range) {
1463
1368
  const cells = {};
1464
- const fields = this.model.projection?.fields ?? [];
1369
+ const fields = this.model.projection?.getFields() ?? [];
1370
+ const currentSort = this.model.getSorting();
1465
1371
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1466
1372
  const { field, props } = this.model.projection.getFieldProjection(fields[col].id);
1467
- const sorting = this.model.sorting?.sorting;
1468
- const direction = sorting?.fieldId === field.id ? sorting.direction : void 0;
1373
+ const direction = currentSort?.fieldId === field.id ? currentSort.direction : void 0;
1469
1374
  cells[toPlaneCellIndex({
1470
1375
  col,
1471
1376
  row: 0
@@ -1474,7 +1379,7 @@ var TablePresentation = class {
1474
1379
  value: "",
1475
1380
  resizeHandle: "col",
1476
1381
  accessoryHtml: `
1477
- <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>
1478
1383
  ${direction !== void 0 ? tableButtons.sort.render({
1479
1384
  fieldId: field.id,
1480
1385
  direction
@@ -1483,7 +1388,7 @@ var TablePresentation = class {
1483
1388
  fieldId: field.id
1484
1389
  })}
1485
1390
  `,
1486
- className: "!bg-toolbarSurface !text-description [&>div]:flex [&>div]:items-stretch"
1391
+ className: "!bg-toolbar-surface text-description! [&>div]:flex [&>div]:items-stretch"
1487
1392
  };
1488
1393
  }
1489
1394
  return cells;
@@ -1534,7 +1439,7 @@ var TablePresentation = class {
1534
1439
  col: 0,
1535
1440
  row: 0
1536
1441
  })]: {
1537
- className: "!bg-toolbarSurface",
1442
+ className: "!bg-toolbar-surface",
1538
1443
  readonly: true,
1539
1444
  value: ""
1540
1445
  }
@@ -1548,9 +1453,9 @@ var TablePresentation = class {
1548
1453
  accessoryHtml: tableControls.checkbox.render({
1549
1454
  rowIndex: 0,
1550
1455
  header: true,
1551
- checked: this.model.selection.allRowsSeleted.value
1456
+ checked: this.model.selection.allRowsSelected
1552
1457
  }),
1553
- className: "!bg-toolbarSurface",
1458
+ className: "!bg-toolbar-surface",
1554
1459
  readonly: true,
1555
1460
  value: ""
1556
1461
  }
@@ -1563,9 +1468,9 @@ var TablePresentation = class {
1563
1468
  row: 0
1564
1469
  })]: {
1565
1470
  accessoryHtml: this.model.features.schemaEditable ? tableButtons.addColumn.render({
1566
- disabled: (this.model.projection?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT
1471
+ disabled: (this.model.projection?.getFields()?.length ?? 0) >= VIEW_FIELD_LIMIT
1567
1472
  }) : void 0,
1568
- className: "!bg-toolbarSurface",
1473
+ className: "!bg-toolbar-surface",
1569
1474
  readonly: true,
1570
1475
  value: ""
1571
1476
  }
@@ -1573,7 +1478,7 @@ var TablePresentation = class {
1573
1478
  }
1574
1479
  getDraftActionCells(range) {
1575
1480
  const cells = {};
1576
- const draftRows = this.model.draftRows.value;
1481
+ const draftRows = this.model.getDraftRows();
1577
1482
  if (draftRows.length === 0) {
1578
1483
  cells[toPlaneCellIndex({
1579
1484
  col: 0,
@@ -1604,7 +1509,7 @@ var TablePresentation = class {
1604
1509
  }
1605
1510
  getDraftIconCells(range) {
1606
1511
  const cells = {};
1607
- const draftRows = this.model.draftRows.value;
1512
+ const draftRows = this.model.getDraftRows();
1608
1513
  for (let row = range.start.row; row <= range.end.row; row++) {
1609
1514
  cells[toPlaneCellIndex({
1610
1515
  col: 0,
@@ -1623,7 +1528,7 @@ var cellClassesForRowSelection = (selected, selectionMode) => {
1623
1528
  if (!selected) {
1624
1529
  if (selectionMode === "single") {
1625
1530
  return [
1626
- "!cursor-pointer"
1531
+ "cursor-pointer!"
1627
1532
  ];
1628
1533
  } else {
1629
1534
  return void 0;
@@ -1632,23 +1537,23 @@ var cellClassesForRowSelection = (selected, selectionMode) => {
1632
1537
  switch (selectionMode) {
1633
1538
  case "single":
1634
1539
  return [
1635
- "!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!"
1636
1541
  ];
1637
1542
  case "multiple":
1638
1543
  return [
1639
- "!bg-gridCellSelected"
1544
+ "bg-grid-cell-selected!"
1640
1545
  ];
1641
1546
  }
1642
1547
  };
1643
1548
 
1644
1549
  // src/hooks/useAddRow.ts
1645
1550
  import { useCallback } from "react";
1646
- import { Obj as Obj4 } from "@dxos/echo";
1647
- var useAddRow = ({ space, schema }) => {
1551
+ import { Obj as Obj3 } from "@dxos/echo";
1552
+ var useAddRow = ({ db, schema }) => {
1648
1553
  return useCallback((data) => {
1649
- if (space && schema) {
1554
+ if (db && schema) {
1650
1555
  try {
1651
- space.db.add(Obj4.make(schema, data ?? {}));
1556
+ db.add(Obj3.make(schema, data ?? {}));
1652
1557
  return "final";
1653
1558
  } catch (error) {
1654
1559
  return "draft";
@@ -1656,129 +1561,137 @@ var useAddRow = ({ space, schema }) => {
1656
1561
  }
1657
1562
  return "final";
1658
1563
  }, [
1659
- space,
1564
+ db,
1660
1565
  schema
1661
1566
  ]);
1662
1567
  };
1663
1568
 
1664
1569
  // src/hooks/useProjectionModel.ts
1665
- import { useMemo } from "react";
1666
- import { Type as Type2 } from "@dxos/echo";
1667
- import { ProjectionModel as ProjectionModel2 } from "@dxos/schema";
1668
- var useProjectionModel = (schema, table) => useMemo(() => {
1669
- if (schema && table?.view.target?.projection) {
1670
- const projection = new ProjectionModel2(Type2.toJsonSchema(schema), table.view.target.projection);
1671
- projection.normalizeView();
1672
- return projection;
1673
- }
1674
- }, [
1675
- schema,
1676
- table?.view.target?.projection
1677
- ]);
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
+ };
1678
1597
 
1679
1598
  // src/hooks/useTableModel.ts
1680
- import { useSignals as _useSignals } from "@preact-signals/safe-react/tracking";
1681
- import { effect as effect3 } from "@preact/signals-core";
1682
- import orderBy2 from "lodash.orderby";
1683
- import { useEffect, useMemo as useMemo2, useState } from "react";
1684
- import { Obj as Obj5 } from "@dxos/echo";
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";
1685
1602
  import { useSelected, useSelectionActions } from "@dxos/react-ui-attention";
1686
1603
  import { isNonNullable } from "@dxos/util";
1687
- var useTableModel = ({ object, projection, rows, rowActions, features, onSelectionChanged, onRowAction, ...props }) => {
1688
- var _effect = _useSignals();
1689
- try {
1690
- const selected = useSelected(object && Obj5.getDXN(object).toString(), "multi");
1691
- const initialSelection = useMemo2(() => selected, [
1692
- object
1693
- ]);
1694
- const [model, setModel] = useState();
1695
- useEffect(() => {
1696
- if (!object || !projection) {
1697
- return;
1698
- }
1699
- let model2;
1700
- const t = setTimeout(async () => {
1701
- model2 = new TableModel({
1702
- object,
1703
- projection,
1704
- features,
1705
- rowActions,
1706
- initialSelection,
1707
- onRowAction,
1708
- ...props
1709
- });
1710
- await model2.open();
1711
- setModel(model2);
1712
- });
1713
- return () => {
1714
- clearTimeout(t);
1715
- void model2?.close();
1716
- };
1717
- }, [
1718
- object,
1719
- projection,
1720
- features,
1721
- rowActions,
1722
- initialSelection
1723
- ]);
1724
- useEffect(() => {
1725
- if (rows) {
1726
- const sortedRows = orderBy2(rows, [
1727
- (row) => String(row.id)
1728
- ], [
1729
- "asc"
1730
- ]);
1731
- model?.setRows(sortedRows);
1732
- }
1733
- }, [
1734
- model,
1735
- rows
1736
- ]);
1737
- const { multiSelect, clear } = useSelectionActions([
1738
- model?.id
1739
- ].filter(isNonNullable));
1740
- useEffect(() => {
1741
- if (!model) {
1742
- return;
1743
- }
1744
- const unsubscribe = effect3(() => {
1745
- const selectedItems = [
1746
- ...model.selection.selection.value
1747
- ];
1748
- multiSelect(selectedItems);
1749
- onSelectionChanged?.(selectedItems);
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
1750
1628
  });
1751
- return () => {
1752
- clear();
1753
- unsubscribe();
1754
- };
1755
- }, [
1756
- model,
1757
- onSelectionChanged
1758
- ]);
1759
- return model;
1760
- } finally {
1761
- _effect.f();
1762
- }
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;
1763
1676
  };
1764
1677
 
1765
1678
  export {
1766
- getBaseSchema,
1767
- makeDynamicTable,
1768
- touch,
1679
+ extractOrder,
1680
+ getSortValue,
1681
+ compareValues,
1769
1682
  BUTTON_IDENTIFIERS,
1770
1683
  tableButtons,
1771
1684
  CONTROL_IDENTIFIERS,
1772
1685
  CheckboxStory,
1773
1686
  SwitchStory,
1774
1687
  tableControls,
1775
- narrowSchema,
1688
+ createEchoChangeCallback,
1689
+ createDirectChangeCallback,
1776
1690
  TableModel,
1777
- ModalController,
1778
1691
  TablePresentation,
1779
1692
  cellClassesForRowSelection,
1780
1693
  useAddRow,
1781
1694
  useProjectionModel,
1782
1695
  useTableModel
1783
1696
  };
1784
- //# sourceMappingURL=chunk-CWSGS3Y2.mjs.map
1697
+ //# sourceMappingURL=chunk-2AN3QQ3D.mjs.map