@dxos/react-ui-table 0.8.4-main.72ec0f3 → 0.8.4-main.765dc60934

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