@dxos/react-ui-table 0.8.4-main.7ace549 → 0.8.4-main.8360d9e660

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 (123) hide show
  1. package/dist/lib/browser/{chunk-5XWSB3JP.mjs → chunk-BF54SSNL.mjs} +27 -28
  2. package/dist/lib/browser/chunk-BF54SSNL.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-KNC7XYT6.mjs → chunk-PTHMYAAM.mjs} +620 -652
  4. package/dist/lib/browser/chunk-PTHMYAAM.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +1154 -1036
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/testing/index.mjs +20 -19
  9. package/dist/lib/browser/testing/index.mjs.map +3 -3
  10. package/dist/lib/browser/types/index.mjs +1 -1
  11. package/dist/lib/node-esm/{chunk-CWSGS3Y2.mjs → chunk-GI72MQSG.mjs} +620 -651
  12. package/dist/lib/node-esm/chunk-GI72MQSG.mjs.map +7 -0
  13. package/dist/lib/node-esm/{chunk-MKTMOY46.mjs → chunk-SIRDIPHZ.mjs} +27 -28
  14. package/dist/lib/node-esm/chunk-SIRDIPHZ.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +1154 -1036
  16. package/dist/lib/node-esm/index.mjs.map +4 -4
  17. package/dist/lib/node-esm/meta.json +1 -1
  18. package/dist/lib/node-esm/testing/index.mjs +20 -19
  19. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  20. package/dist/lib/node-esm/types/index.mjs +1 -1
  21. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -2
  22. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  23. package/dist/types/src/components/Table/ColumnSettings.d.ts +2 -2
  24. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  25. package/dist/types/src/components/Table/DynamicTable.d.ts +7 -7
  26. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +2 -16
  28. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/Relations.stories.d.ts +6 -3
  30. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
  31. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  32. package/dist/types/src/components/Table/Table.d.ts +11 -11
  33. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  34. package/dist/types/src/components/Table/Table.stories.d.ts +6 -3
  35. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  36. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +1 -1
  37. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +10 -12
  38. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  39. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +5 -7
  40. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  41. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +2 -2
  42. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  43. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +3 -3
  44. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
  45. package/dist/types/src/components/testing.d.ts +22 -13
  46. package/dist/types/src/components/testing.d.ts.map +1 -1
  47. package/dist/types/src/hooks/useAddRow.d.ts +5 -6
  48. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  49. package/dist/types/src/hooks/useProjectionModel.d.ts +2 -1
  50. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -1
  51. package/dist/types/src/hooks/useTableModel.d.ts +6 -5
  52. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  53. package/dist/types/src/model/modal-controller.d.ts +5 -1
  54. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  55. package/dist/types/src/model/selection-model.d.ts +17 -8
  56. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  57. package/dist/types/src/model/table-model.d.ts +100 -18
  58. package/dist/types/src/model/table-model.d.ts.map +1 -1
  59. package/dist/types/src/model/table-presentation.d.ts +3 -1
  60. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  61. package/dist/types/src/playwright/TableManager.d.ts +2 -1
  62. package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
  63. package/dist/types/src/testing/useTestTableModel.d.ts +5 -8
  64. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  65. package/dist/types/src/translations.d.ts +2 -2
  66. package/dist/types/src/translations.d.ts.map +1 -1
  67. package/dist/types/src/types/Table.d.ts +13 -15
  68. package/dist/types/src/types/Table.d.ts.map +1 -1
  69. package/dist/types/src/util/dynamic-table.d.ts +12 -10
  70. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  71. package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
  72. package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
  73. package/dist/types/src/util/index.d.ts +2 -0
  74. package/dist/types/src/util/index.d.ts.map +1 -1
  75. package/dist/types/src/util/query-ast.d.ts +7 -0
  76. package/dist/types/src/util/query-ast.d.ts.map +1 -0
  77. package/dist/types/src/util/sort.d.ts +12 -0
  78. package/dist/types/src/util/sort.d.ts.map +1 -0
  79. package/dist/types/tsconfig.tsbuildinfo +1 -1
  80. package/package.json +54 -55
  81. package/src/components/Table/ColumnActionsMenu.tsx +10 -6
  82. package/src/components/Table/ColumnSettings.tsx +8 -9
  83. package/src/components/Table/DynamicTable.stories.tsx +11 -11
  84. package/src/components/Table/DynamicTable.tsx +30 -30
  85. package/src/components/Table/Relations.stories.tsx +34 -31
  86. package/src/components/Table/RowActionsMenu.tsx +3 -2
  87. package/src/components/Table/Table.stories.tsx +80 -67
  88. package/src/components/Table/Table.tsx +324 -299
  89. package/src/components/TableCellEditor/CellValidationMessage.tsx +5 -5
  90. package/src/components/TableCellEditor/FormCellEditor.tsx +118 -135
  91. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +10 -7
  92. package/src/components/TableCellEditor/TableCellEditor.tsx +27 -27
  93. package/src/components/TableToolbar/TableToolbar.tsx +24 -18
  94. package/src/components/testing.ts +37 -11
  95. package/src/hooks/useAddRow.ts +8 -10
  96. package/src/hooks/useProjectionModel.ts +29 -9
  97. package/src/hooks/useTableModel.ts +27 -23
  98. package/src/model/modal-controller.ts +23 -12
  99. package/src/model/selection-model.ts +75 -44
  100. package/src/model/table-model.test.ts +81 -53
  101. package/src/model/table-model.ts +410 -138
  102. package/src/model/table-presentation.test.ts +58 -48
  103. package/src/model/table-presentation.ts +34 -28
  104. package/src/playwright/TableManager.ts +12 -5
  105. package/src/playwright/playwright.config.ts +1 -1
  106. package/src/playwright/smoke.spec.ts +48 -4
  107. package/src/testing/useTestTableModel.ts +24 -25
  108. package/src/translations.ts +1 -1
  109. package/src/types/Table.ts +28 -38
  110. package/src/util/dynamic-table.test.ts +70 -0
  111. package/src/util/dynamic-table.ts +36 -19
  112. package/src/util/index.ts +2 -0
  113. package/src/util/query-ast.ts +22 -0
  114. package/src/util/sort.ts +90 -0
  115. package/src/util/table-buttons.ts +1 -1
  116. package/src/util/table-controls.ts +2 -2
  117. package/dist/lib/browser/chunk-5XWSB3JP.mjs.map +0 -7
  118. package/dist/lib/browser/chunk-KNC7XYT6.mjs.map +0 -7
  119. package/dist/lib/node-esm/chunk-CWSGS3Y2.mjs.map +0 -7
  120. package/dist/lib/node-esm/chunk-MKTMOY46.mjs.map +0 -7
  121. package/dist/types/src/model/table-sorting.d.ts +0 -56
  122. package/dist/types/src/model/table-sorting.d.ts.map +0 -1
  123. 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.change(table, (mutableTable) => mutate(mutableTable)),
389
+ row: (row, mutate) => {
390
+ if (Obj.isObject(row)) {
391
+ Obj.change(row, (mutableRow) => mutate(mutableRow));
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,80 +407,146 @@ 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
456
  invariant(!(this._features.dataEditable && this._features.selection.enabled && this._features.selection.mode === "single"), "Single selection is not compatible with editable tables.", {
615
457
  F: __dxlog_file,
616
- L: 137,
458
+ L: 197,
617
459
  S: this,
618
460
  A: [
619
461
  "!(this._features.dataEditable && this._features.selection.enabled && this._features.selection.mode === 'single')",
620
462
  "'Single selection is not compatible with editable tables.'"
621
463
  ]
622
464
  });
623
- this._sorting = new TableSorting({
624
- rows: this._rows,
625
- getView: () => this.view,
626
- projection: this._projection
465
+ this._visibleRange = Atom2.make({
466
+ start: {
467
+ row: 0,
468
+ col: 0
469
+ },
470
+ end: {
471
+ row: 0,
472
+ col: 0
473
+ }
474
+ });
475
+ this._rows = Atom2.make([]);
476
+ this._draftRows = Atom2.make([]);
477
+ this._inMemorySort = Atom2.make(void 0);
478
+ this._cellUpdateCounter = Atom2.make(0);
479
+ this._persistedSort = Atom2.make((_get) => {
480
+ const viewSnapshot = Obj.getSnapshot(this.view);
481
+ const ast = viewSnapshot.query.ast;
482
+ const orders = extractOrder(ast);
483
+ if (orders && orders.length > 0) {
484
+ const firstOrder = orders[0];
485
+ if (firstOrder.kind === "property") {
486
+ const field = this._projection.getFields().find((f) => f.path === firstOrder.property);
487
+ if (field) {
488
+ return {
489
+ fieldId: field.id,
490
+ direction: firstOrder.direction
491
+ };
492
+ }
493
+ }
494
+ }
495
+ return void 0;
496
+ });
497
+ this._sorting = Atom2.make((get) => {
498
+ const inMemorySort = get(this._inMemorySort);
499
+ return inMemorySort ?? get(this._persistedSort);
500
+ });
501
+ this._sortedRows = Atom2.make((get) => {
502
+ const rows = get(this._rows);
503
+ const sort = get(this._sorting);
504
+ if (!sort || rows.length === 0) {
505
+ return rows;
506
+ }
507
+ const field = this._projection.getFields().find((f) => f.id === sort.fieldId);
508
+ if (!field) {
509
+ return rows;
510
+ }
511
+ const fieldProjection = this._projection.getFieldProjection(field.id);
512
+ const sorted = [
513
+ ...rows
514
+ ].sort((a, b) => {
515
+ const aValue = getValue(a, field.path);
516
+ const bValue = getValue(b, field.path);
517
+ return compareValues(aValue, bValue, fieldProjection.props.format, sort.direction);
518
+ });
519
+ return sorted;
520
+ });
521
+ this._selection = new SelectionModel(this._registry, this._sortedRows, this._features.selection.mode ?? "multiple", initialSelection, () => this._onRowOrderChange?.());
522
+ this._viewDirty = Atom2.make((get) => {
523
+ const inMemorySort = get(this._inMemorySort);
524
+ if (!inMemorySort) {
525
+ return false;
526
+ }
527
+ const persisted = get(this._persistedSort);
528
+ if (!persisted) {
529
+ return true;
530
+ }
531
+ return inMemorySort.fieldId !== persisted.fieldId || inMemorySort.direction !== persisted.direction;
627
532
  });
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
533
  this._pinnedRows = pinnedRows;
634
534
  this._rowActions = rowActions;
635
535
  this._onInsertRow = onInsertRow;
636
536
  this._onDeleteRows = onDeleteRows;
637
- this._onDeleteColumn = onDeleteColumn;
537
+ this._onColumnDelete = onColumnDelete;
638
538
  this._onCellUpdate = onCellUpdate;
639
539
  this._onRowOrderChange = onRowOrderChange;
640
540
  this._onRowAction = onRowAction;
641
541
  }
642
542
  get id() {
643
- return Obj2.getDXN(this._object).toString();
543
+ return Obj.getDXN(this._object).toString();
644
544
  }
645
545
  get view() {
646
546
  const view = this._object.view.target;
647
547
  invariant(view, "Table model not initialized", {
648
548
  F: __dxlog_file,
649
- L: 176,
549
+ L: 308,
650
550
  S: this,
651
551
  A: [
652
552
  "view",
@@ -661,26 +561,52 @@ var TableModel = class extends Resource2 {
661
561
  get projection() {
662
562
  return this._projection;
663
563
  }
564
+ get db() {
565
+ return this._db;
566
+ }
567
+ /**
568
+ * Atom for reactive access to rows with in-memory sorting applied.
569
+ */
664
570
  get rows() {
665
- return this.sorting.sortedRows;
571
+ return this._sortedRows;
572
+ }
573
+ /**
574
+ * Gets rows with in-memory sorting applied.
575
+ * In-memory sort is local to this instance until saved.
576
+ */
577
+ getRows() {
578
+ return this._registry.get(this._sortedRows);
579
+ }
580
+ /** Atom for reactive access to cell changes. */
581
+ get cellUpdate() {
582
+ return this._cellUpdateCounter;
583
+ }
584
+ /**
585
+ * Change a row using the configured change callback.
586
+ * Use this instead of directly mutating to ensure consistent mutation handling.
587
+ */
588
+ changeRow(row, mutate) {
589
+ this._change.row(row, mutate);
666
590
  }
667
591
  get features() {
668
592
  return this._features;
669
593
  }
670
594
  /**
671
- * @reactive
595
+ * Gets the current sort state.
596
+ * Priority: in-memory sort (local changes) > persisted sort (from query AST).
672
597
  */
673
- get sorting() {
674
- return this._sorting;
598
+ getSorting() {
599
+ return this._registry.get(this._sorting);
675
600
  }
676
601
  /**
677
602
  * Gets the row data at the specified display index.
678
603
  */
679
604
  getRowAt(displayIndex) {
680
- if (displayIndex < 0 || displayIndex >= this.sorting.sortedRows.value.length) {
605
+ const sortedRows = this._registry.get(this._sortedRows);
606
+ if (displayIndex < 0 || displayIndex >= sortedRows.length) {
681
607
  return void 0;
682
608
  }
683
- return this.sorting.sortedRows.value[displayIndex];
609
+ return sortedRows[displayIndex];
684
610
  }
685
611
  get pinnedRows() {
686
612
  return this._pinnedRows;
@@ -688,7 +614,7 @@ var TableModel = class extends Resource2 {
688
614
  get columnMeta() {
689
615
  invariant(this._columnMeta, void 0, {
690
616
  F: __dxlog_file,
691
- L: 219,
617
+ L: 381,
692
618
  S: this,
693
619
  A: [
694
620
  "this._columnMeta",
@@ -700,10 +626,6 @@ var TableModel = class extends Resource2 {
700
626
  get selection() {
701
627
  return this._selection;
702
628
  }
703
- /** @reactive */
704
- get isViewDirty() {
705
- return this.sorting.isDirty;
706
- }
707
629
  get rowActions() {
708
630
  return this._rowActions;
709
631
  }
@@ -717,8 +639,17 @@ var TableModel = class extends Resource2 {
717
639
  await this._selection.open(this._ctx);
718
640
  }
719
641
  initializeColumnMeta() {
720
- this._columnMeta = computed3(() => {
721
- const fields = this._projection?.fields ?? [];
642
+ invariant(this._object.view.target, "View must be loaded before initializing column meta", {
643
+ F: __dxlog_file,
644
+ L: 405,
645
+ S: this,
646
+ A: [
647
+ "this._object.view.target",
648
+ "'View must be loaded before initializing column meta'"
649
+ ]
650
+ });
651
+ this._columnMeta = Atom2.make((get) => {
652
+ const fields = get(this._projection.fields);
722
653
  const meta = Object.fromEntries(fields.map((field, index) => [
723
654
  index,
724
655
  {
@@ -744,49 +675,62 @@ var TableModel = class extends Resource2 {
744
675
  });
745
676
  }
746
677
  initializeEffects() {
747
- const rowOrderWatcher = effect2(() => {
748
- touch(this.sorting.sortedRows.value);
678
+ const rowsUnsubscribe = this._registry.subscribe(this._rows, () => {
749
679
  this._onRowOrderChange?.();
750
680
  });
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"
681
+ this._ctx.onDispose(rowsUnsubscribe);
682
+ const rowSubscriptions = /* @__PURE__ */ new Map();
683
+ const visibleRangeUnsubscribe = this._registry.subscribe(this._visibleRange, () => {
684
+ for (const [id, unsub] of rowSubscriptions) {
685
+ unsub();
686
+ rowSubscriptions.delete(id);
687
+ }
688
+ const { start, end } = this._registry.get(this._visibleRange);
689
+ const sortedRows = this._registry.get(this._sortedRows);
690
+ for (let row = start.row; row <= end.row && row < sortedRows.length; row++) {
691
+ const obj = sortedRows[row];
692
+ if (Obj.isObject(obj) && !rowSubscriptions.has(obj.id)) {
693
+ const unsub = Obj.subscribe(obj, () => {
694
+ this._registry.set(this._cellUpdateCounter, this._registry.get(this._cellUpdateCounter) + 1);
695
+ this._onCellUpdate?.({
696
+ row,
697
+ col: start.col,
698
+ plane: "grid"
699
+ });
763
700
  });
764
- }));
701
+ rowSubscriptions.set(obj.id, unsub);
702
+ }
765
703
  }
766
- return () => rowEffects.forEach((cleanup) => cleanup());
767
704
  });
768
- this._ctx.onDispose(rowEffectManager);
769
- const draftRowsWatcher = effect2(() => {
770
- touch(this._draftRows.value);
705
+ this._ctx.onDispose(() => {
706
+ visibleRangeUnsubscribe();
707
+ for (const unsub of rowSubscriptions.values()) {
708
+ unsub();
709
+ }
710
+ });
711
+ const draftRowsUnsubscribe = this._registry.subscribe(this._draftRows, () => {
771
712
  this._onRowOrderChange?.();
772
713
  });
773
- this._ctx.onDispose(draftRowsWatcher);
714
+ this._ctx.onDispose(draftRowsUnsubscribe);
774
715
  }
775
716
  //
776
717
  // Data
777
718
  //
778
719
  setRows = (obj) => {
779
- this._rows.value = obj;
720
+ this._registry.set(this._rows, obj);
780
721
  };
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.
722
+ getRowCount = () => this._registry.get(this._sortedRows).length;
723
+ getDraftRowCount() {
724
+ return this._registry.get(this._draftRows).length;
725
+ }
726
+ /** Atom for reactive access to draft rows. */
787
727
  get draftRows() {
788
728
  return this._draftRows;
789
729
  }
730
+ /** Gets the current draft rows. */
731
+ getDraftRows() {
732
+ return this._registry.get(this._draftRows);
733
+ }
790
734
  /**
791
735
  * Checks if a specific field path has validation errors for a given draft row.
792
736
  * @param draftRowIndex - The index of the draft row to check
@@ -794,17 +738,17 @@ var TableModel = class extends Resource2 {
794
738
  * @returns true if the field has validation errors, false otherwise
795
739
  */
796
740
  hasDraftRowValidationError(draftRowIndex, fieldPath) {
797
- if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.value.length) {
741
+ if (draftRowIndex < 0 || draftRowIndex >= this._registry.get(this._draftRows).length) {
798
742
  return false;
799
743
  }
800
- const draftRow = this._draftRows.value[draftRowIndex];
744
+ const draftRow = this._registry.get(this._draftRows)[draftRowIndex];
801
745
  const validationErrors = draftRow.validationErrors || [];
802
746
  return validationErrors.some((error) => error.path === fieldPath);
803
747
  }
804
- getColumnCount = () => this._projection?.fields.length ?? 0;
748
+ getColumnCount = () => this._projection?.getFields().length ?? 0;
805
749
  insertRow = () => {
806
750
  const result = this._onInsertRow?.();
807
- if (result === "draft" && this._draftRows.value.length === 0) {
751
+ if (result === "draft" && this._registry.get(this._draftRows).length === 0) {
808
752
  this.createDraftRow();
809
753
  }
810
754
  return result ?? "final";
@@ -822,16 +766,16 @@ var TableModel = class extends Resource2 {
822
766
  valid: validationErrors.length === 0,
823
767
  validationErrors
824
768
  };
825
- this._draftRows.value = [
826
- ...this._draftRows.value,
769
+ this._registry.set(this._draftRows, [
770
+ ...this._registry.get(this._draftRows),
827
771
  draftRow
828
- ];
772
+ ]);
829
773
  }
830
774
  validateDraftRow(draftRowIndex) {
831
- if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.value.length) {
775
+ if (draftRowIndex < 0 || draftRowIndex >= this._registry.get(this._draftRows).length) {
832
776
  return;
833
777
  }
834
- const draftRow = this._draftRows.value[draftRowIndex];
778
+ const draftRow = this._registry.get(this._draftRows)[draftRowIndex];
835
779
  const validationErrors = this.validateDraftRowData(draftRow.data);
836
780
  const updatedDraftRow = {
837
781
  ...draftRow,
@@ -839,69 +783,71 @@ var TableModel = class extends Resource2 {
839
783
  validationErrors
840
784
  };
841
785
  const newDraftRows = [
842
- ...this._draftRows.value
786
+ ...this._registry.get(this._draftRows)
843
787
  ];
844
788
  newDraftRows[draftRowIndex] = updatedDraftRow;
845
- this._draftRows.value = newDraftRows;
789
+ this._registry.set(this._draftRows, newDraftRows);
846
790
  }
847
791
  validateDraftRowData(data) {
848
792
  const schema = toEffectSchema(this._projection.baseSchema);
849
793
  return validateSchema(schema, data) || [];
850
794
  }
851
795
  commitDraftRow = (draftRowIndex) => {
852
- if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.value.length) {
796
+ if (draftRowIndex < 0 || draftRowIndex >= this._registry.get(this._draftRows).length) {
853
797
  return false;
854
798
  }
855
- const draftRow = this._draftRows.value[draftRowIndex];
799
+ const draftRow = this._registry.get(this._draftRows)[draftRowIndex];
856
800
  if (!draftRow.valid) {
857
801
  return false;
858
802
  }
859
803
  const insertRowResult = this._onInsertRow?.(draftRow.data);
860
804
  if (insertRowResult === "final") {
861
- const newDraftRows = this._draftRows.value.filter((_, index) => index !== draftRowIndex);
862
- this._draftRows.value = newDraftRows;
805
+ const newDraftRows = this._registry.get(this._draftRows).filter((_, index) => index !== draftRowIndex);
806
+ this._registry.set(this._draftRows, newDraftRows);
863
807
  }
864
808
  return insertRowResult === "final";
865
809
  };
866
810
  deleteRow = (rowIndex) => {
867
- const row = this._sorting.getDataIndex(rowIndex);
868
- const obj = this._rows.value[row];
811
+ const sortedRows = this._registry.get(this._sortedRows);
812
+ const obj = sortedRows[rowIndex];
869
813
  const objectsToDelete = [];
870
- if (this.features.selection.enabled && this._selection.hasSelection.value) {
814
+ if (this.features.selection.enabled && this._selection.hasSelection) {
871
815
  const selectedRows = this._selection.getSelectedRows();
872
816
  objectsToDelete.push(...selectedRows);
873
- } else {
817
+ } else if (obj) {
874
818
  objectsToDelete.push(obj);
875
819
  }
876
- this._onDeleteRows?.(row, objectsToDelete);
820
+ if (objectsToDelete.length > 0) {
821
+ this._onDeleteRows?.(rowIndex, objectsToDelete);
822
+ }
877
823
  };
878
824
  handleRowAction = (actionId, rowIndex) => {
879
825
  if (!this._onRowAction) {
880
826
  return;
881
827
  }
882
- const row = this._sorting.getDataIndex(rowIndex);
883
- const data = this._rows.value[row];
828
+ const sortedRows = this._registry.get(this._sortedRows);
829
+ const data = sortedRows[rowIndex];
884
830
  if (data) {
885
831
  this._onRowAction(actionId, data);
886
832
  }
887
833
  };
888
834
  getCellData = (cell) => {
889
835
  const { col, row, plane } = cell;
890
- const fields = this._projection?.fields ?? [];
836
+ const fields = this._projection?.getFields() ?? [];
891
837
  if (col < 0 || col >= fields.length) {
892
838
  return void 0;
893
839
  }
894
840
  const field = fields[col];
895
841
  let value;
896
842
  if (plane === "frozenRowsEnd") {
897
- if (row >= 0 && row < this._draftRows.value.length) {
898
- value = getValue2(this._draftRows.value[row].data, field.path);
843
+ if (row >= 0 && row < this._registry.get(this._draftRows).length) {
844
+ value = getValue(this._registry.get(this._draftRows)[row].data, field.path);
899
845
  } else {
900
846
  return void 0;
901
847
  }
902
848
  } else {
903
- const dataIndex = this._sorting.getDataIndex(row);
904
- value = getValue2(this._rows.value[dataIndex], field.path);
849
+ const sortedRows = this._registry.get(this._sortedRows);
850
+ value = getValue(sortedRows[row], field.path);
905
851
  }
906
852
  if (value == null) {
907
853
  return "";
@@ -912,7 +858,7 @@ var TableModel = class extends Resource2 {
912
858
  if (!field.referencePath) {
913
859
  return "";
914
860
  }
915
- return getValue2(value.target, field.referencePath);
861
+ return getValue(value.target, field.referencePath);
916
862
  }
917
863
  default: {
918
864
  return formatForEditing({
@@ -924,7 +870,7 @@ var TableModel = class extends Resource2 {
924
870
  }
925
871
  };
926
872
  validateCellData = async ({ col, row, plane }, value) => {
927
- const fields = this._projection?.fields ?? [];
873
+ const fields = this._projection?.getFields() ?? [];
928
874
  if (col < 0 || col >= fields.length) {
929
875
  return {
930
876
  valid: false,
@@ -936,14 +882,14 @@ var TableModel = class extends Resource2 {
936
882
  const transformedValue = editorTextToCellValue(props, value);
937
883
  const isDraftRow = plane === "frozenRowsEnd";
938
884
  if (isDraftRow) {
939
- const isInvalidDraftRowIndex = row < 0 || row >= this._draftRows.value.length;
885
+ const isInvalidDraftRowIndex = row < 0 || row >= this._registry.get(this._draftRows).length;
940
886
  if (isInvalidDraftRowIndex) {
941
887
  return {
942
888
  valid: false,
943
889
  error: "Invalid draft row index"
944
890
  };
945
891
  }
946
- const draftRow = this._draftRows.value[row];
892
+ const draftRow = this._registry.get(this._draftRows)[row];
947
893
  const snapshot = {
948
894
  ...draftRow.data
949
895
  };
@@ -962,23 +908,24 @@ var TableModel = class extends Resource2 {
962
908
  valid: true
963
909
  };
964
910
  } else {
965
- const rowIdx = this._sorting.getDataIndex(row);
966
- const currentRow = this._rows.value[rowIdx];
911
+ const currentRow = this._registry.get(this._rows)[row];
967
912
  invariant(currentRow, "Invalid row index", {
968
913
  F: __dxlog_file,
969
- L: 516,
914
+ L: 693,
970
915
  S: this,
971
916
  A: [
972
917
  "currentRow",
973
918
  "'Invalid row index'"
974
919
  ]
975
920
  });
976
- const snapshot = getSnapshot(currentRow);
921
+ const snapshot = {
922
+ ...getSnapshot(currentRow)
923
+ };
977
924
  setValue(snapshot, field.path, transformedValue);
978
- const schema = Obj2.getSchema(currentRow);
925
+ const schema = Obj.getSchema(currentRow);
979
926
  invariant(schema, void 0, {
980
927
  F: __dxlog_file,
981
- L: 522,
928
+ L: 699,
982
929
  S: this,
983
930
  A: [
984
931
  "schema",
@@ -990,7 +937,7 @@ var TableModel = class extends Resource2 {
990
937
  const error = validationResult[0];
991
938
  invariant(error.path === field.path, void 0, {
992
939
  F: __dxlog_file,
993
- L: 527,
940
+ L: 704,
994
941
  S: this,
995
942
  A: [
996
943
  "error.path === field.path",
@@ -1009,24 +956,27 @@ var TableModel = class extends Resource2 {
1009
956
  };
1010
957
  setCellData = (cell, value) => {
1011
958
  const { col, row, plane } = cell;
1012
- const fields = this._projection?.fields ?? [];
959
+ const fields = this._projection?.getFields() ?? [];
1013
960
  if (col < 0 || col >= fields.length) {
1014
961
  return;
1015
962
  }
1016
963
  const field = fields[col];
1017
964
  const { props } = this._projection.getFieldProjection(field.id);
1018
965
  const transformedValue = editorTextToCellValue(props, value);
1019
- if (props.format === Format2.TypeFormat.Ref && !isLiveObject(value)) {
966
+ if (props.format === Format2.TypeFormat.Ref && !Obj.isObject(value)) {
1020
967
  return;
1021
968
  }
1022
969
  if (plane === "frozenRowsEnd") {
1023
- if (row >= 0 && row < this._draftRows.value.length) {
1024
- setValue(this._draftRows.value[row].data, field.path, transformedValue);
970
+ if (row >= 0 && row < this._registry.get(this._draftRows).length) {
971
+ setValue(this._registry.get(this._draftRows)[row].data, field.path, transformedValue);
1025
972
  this.validateDraftRow(row);
1026
973
  }
1027
974
  } else {
1028
- const rowIdx = this._sorting.getDataIndex(row);
1029
- setValue(this._rows.value[rowIdx], field.path, transformedValue);
975
+ const sortedRows = this._registry.get(this._sortedRows);
976
+ const rowData = sortedRows[row];
977
+ this._change.row(rowData, (mutableRow) => {
978
+ setValue(mutableRow, field.path, transformedValue);
979
+ });
1030
980
  }
1031
981
  };
1032
982
  /**
@@ -1035,12 +985,15 @@ var TableModel = class extends Resource2 {
1035
985
  * @param {(value: any) => any} update - A function that takes the current value and returns the updated value.
1036
986
  */
1037
987
  updateCellData({ col, row }, update) {
1038
- const dataRow = this._sorting.getDataIndex(row);
1039
- const fields = this._projection?.fields ?? [];
988
+ const fields = this._projection?.getFields() ?? [];
1040
989
  const field = fields[col];
1041
- const value = getValue2(this._rows.value[dataRow], field.path);
990
+ const sortedRows = this._registry.get(this._sortedRows);
991
+ const rowData = sortedRows[row];
992
+ const value = getValue(rowData, field.path);
1042
993
  const updatedValue = update(value);
1043
- setValue(this._rows.value[dataRow], field.path, updatedValue);
994
+ this._change.row(rowData, (mutableRow) => {
995
+ setValue(mutableRow, field.path, updatedValue);
996
+ });
1044
997
  }
1045
998
  /**
1046
999
  * Gets the domain-appropriate cell type for a given cell position.
@@ -1074,21 +1027,23 @@ var TableModel = class extends Resource2 {
1074
1027
  if (!this._projection) {
1075
1028
  return;
1076
1029
  }
1077
- const field = this._projection?.fields.find((field2) => field2.id === fieldId);
1078
- if (field && this._onDeleteColumn) {
1079
- this._onDeleteColumn(field.id);
1030
+ const field = this._projection?.getFields().find((field2) => field2.id === fieldId);
1031
+ if (field && this._onColumnDelete) {
1032
+ this._onColumnDelete(field.id);
1080
1033
  }
1081
1034
  }
1082
1035
  //
1083
1036
  // Resize
1084
1037
  //
1085
1038
  setColumnWidth(columnIndex, width) {
1086
- const fields = this._projection?.fields ?? [];
1039
+ const fields = this._projection?.getFields() ?? [];
1087
1040
  if (columnIndex < fields.length) {
1088
1041
  const newWidth = Math.max(0, width);
1089
1042
  const field = fields[columnIndex];
1090
1043
  if (field) {
1091
- this.table.sizes[field.path] = newWidth;
1044
+ this._change.table((mutableTable) => {
1045
+ mutableTable.sizes[field.path] = newWidth;
1046
+ });
1092
1047
  }
1093
1048
  }
1094
1049
  }
@@ -1104,17 +1059,87 @@ var TableModel = class extends Resource2 {
1104
1059
  this._pinnedRows.bottom = this._pinnedRows.bottom.filter((index) => index !== rowIndex);
1105
1060
  }
1106
1061
  //
1107
- // View operations
1062
+ // Sort operations
1108
1063
  //
1064
+ /**
1065
+ * Sets the sort order for a field in-memory (local to this instance).
1066
+ * Use saveView() to persist the sort to the view query.
1067
+ */
1068
+ setSort(fieldId, direction) {
1069
+ const field = this._projection.getFields().find((f) => f.id === fieldId);
1070
+ if (!field) {
1071
+ return;
1072
+ }
1073
+ this._registry.set(this._inMemorySort, {
1074
+ fieldId,
1075
+ direction
1076
+ });
1077
+ }
1078
+ /**
1079
+ * Toggles the sort direction for a field in-memory.
1080
+ */
1081
+ toggleSort(fieldId) {
1082
+ const currentSort = this.getSorting();
1083
+ if (!currentSort || currentSort.fieldId !== fieldId) {
1084
+ this.setSort(fieldId, "asc");
1085
+ return;
1086
+ }
1087
+ const newDirection = currentSort.direction === "asc" ? "desc" : "asc";
1088
+ this.setSort(fieldId, newDirection);
1089
+ }
1090
+ /**
1091
+ * Clears the in-memory sort order.
1092
+ */
1093
+ clearSort() {
1094
+ this._registry.set(this._inMemorySort, void 0);
1095
+ }
1096
+ /**
1097
+ * Saves the current in-memory sort to the view query AST.
1098
+ * This persists the sort so it becomes the default for all viewers.
1099
+ */
1109
1100
  saveView() {
1110
- this._sorting.save();
1101
+ const view = this.view;
1102
+ const inMemorySort = this._registry.get(this._inMemorySort);
1103
+ const viewSnapshot = Obj.getSnapshot(view);
1104
+ const currentQuery = Query.fromAst(viewSnapshot.query.ast);
1105
+ const baseQuery = this._getBaseQuery(currentQuery);
1106
+ if (inMemorySort) {
1107
+ const field = this._projection.getFields().find((f) => f.id === inMemorySort.fieldId);
1108
+ if (field) {
1109
+ const newQuery = baseQuery.orderBy(Order.property(field.path, inMemorySort.direction));
1110
+ Obj.change(view, (v) => {
1111
+ v.query.ast = newQuery.ast;
1112
+ });
1113
+ }
1114
+ } else {
1115
+ Obj.change(view, (v) => {
1116
+ v.query.ast = baseQuery.ast;
1117
+ });
1118
+ }
1119
+ this._registry.set(this._inMemorySort, void 0);
1120
+ }
1121
+ /**
1122
+ * Extracts the base query without order clauses.
1123
+ */
1124
+ _getBaseQuery(query) {
1125
+ const ast = query.ast;
1126
+ if (ast.type === "order") {
1127
+ return Query.fromAst(ast.query);
1128
+ }
1129
+ return query;
1130
+ }
1131
+ /**
1132
+ * Checks if the view has unsaved changes (in-memory sort differs from persisted sort).
1133
+ */
1134
+ getViewDirty() {
1135
+ return this._registry.get(this._viewDirty);
1111
1136
  }
1112
1137
  };
1113
1138
  var editorTextToCellValue = (props, value) => {
1114
1139
  switch (props.format) {
1115
1140
  case Format2.TypeFormat.Ref: {
1116
- if (isLiveObject(value)) {
1117
- return Ref2.make(value);
1141
+ if (Obj.isObject(value)) {
1142
+ return Ref.make(value);
1118
1143
  } else {
1119
1144
  return value;
1120
1145
  }
@@ -1141,104 +1166,40 @@ var editorTextToCellValue = (props, value) => {
1141
1166
  }
1142
1167
  };
1143
1168
 
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
1169
  // src/model/table-presentation.ts
1211
- import { signal as signal5 } from "@preact/signals-core";
1170
+ import { Atom as Atom3 } from "@effect-atom/atom-react";
1212
1171
  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";
1172
+ import { Obj as Obj2 } from "@dxos/echo";
1173
+ import { Format as Format3, TypeEnum, getValue as getValue2 } from "@dxos/echo/internal";
1174
+ import { cellClassesForFieldType, formatForDisplay } from "@dxos/react-ui-form";
1216
1175
  import { toPlaneCellIndex } from "@dxos/react-ui-grid";
1217
- import { mx } from "@dxos/react-ui-theme";
1218
1176
  import { VIEW_FIELD_LIMIT } from "@dxos/schema";
1177
+ import { mx } from "@dxos/ui-theme";
1219
1178
  var TablePresentation = class {
1220
1179
  model;
1180
+ _registry;
1221
1181
  _visibleRange;
1222
1182
  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
- })) {
1183
+ constructor(registry, model) {
1233
1184
  this.model = model;
1234
- this._visibleRange = _visibleRange;
1185
+ this._registry = registry;
1186
+ this._visibleRange = Atom3.make({
1187
+ start: {
1188
+ row: 0,
1189
+ col: 0
1190
+ },
1191
+ end: {
1192
+ row: 0,
1193
+ col: 0
1194
+ }
1195
+ });
1235
1196
  }
1236
1197
  getCells(range, plane) {
1237
1198
  this.fieldProjectionCache.clear();
1238
1199
  let cells;
1239
1200
  switch (plane) {
1240
1201
  case "grid":
1241
- this._visibleRange.value = range;
1202
+ this._registry.set(this._visibleRange, range);
1242
1203
  cells = this.getMainGridCells(range);
1243
1204
  break;
1244
1205
  case "frozenRowsStart":
@@ -1290,7 +1251,7 @@ var TablePresentation = class {
1290
1251
  const { props } = this.getFieldProjection(field.id);
1291
1252
  const cell = {
1292
1253
  get value() {
1293
- const value = getValue3(obj, field.path);
1254
+ const value = getValue2(obj, field.path);
1294
1255
  if (value == null) {
1295
1256
  return "";
1296
1257
  }
@@ -1307,10 +1268,10 @@ var TablePresentation = class {
1307
1268
  if (!field.referencePath) {
1308
1269
  return "";
1309
1270
  }
1310
- return getValue3(value.target, field.referencePath);
1271
+ return getValue2(value.target, field.referencePath);
1311
1272
  }
1312
1273
  default: {
1313
- return formatForDisplay2({
1274
+ return formatForDisplay({
1314
1275
  type: props.type,
1315
1276
  format: props.format,
1316
1277
  value
@@ -1329,8 +1290,8 @@ var TablePresentation = class {
1329
1290
  classes.push(rowSelectionClasses);
1330
1291
  }
1331
1292
  cell.className = mx(classes);
1332
- if (props.type === TypeEnum2.Array) {
1333
- const targetArray = getValue3(obj, field.path);
1293
+ if (props.type === TypeEnum.Array) {
1294
+ const targetArray = getValue2(obj, field.path);
1334
1295
  if (targetArray && Array.isArray(targetArray)) {
1335
1296
  const getLabel = (value) => {
1336
1297
  if (!value) {
@@ -1357,14 +1318,14 @@ var TablePresentation = class {
1357
1318
  }
1358
1319
  }
1359
1320
  if (props.format === Format3.TypeFormat.Ref && props.referenceSchema) {
1360
- const targetObj = getValue3(obj, field.path)?.target;
1321
+ const targetObj = getValue2(obj, field.path)?.target;
1361
1322
  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>`;
1323
+ const dxn = Obj2.getDXN(targetObj)?.toString();
1324
+ 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
1325
  }
1365
1326
  }
1366
1327
  if (props.format === Format3.TypeFormat.Boolean) {
1367
- const value = getValue3(obj, field.path);
1328
+ const value = getValue2(obj, field.path);
1368
1329
  cell.accessoryHtml = tableControls.switch.render({
1369
1330
  colIndex,
1370
1331
  rowIndex: displayIndex,
@@ -1373,7 +1334,7 @@ var TablePresentation = class {
1373
1334
  cell.readonly = "no-text-select";
1374
1335
  }
1375
1336
  if (props.format === Format3.TypeFormat.SingleSelect) {
1376
- const value = getValue3(obj, field.path);
1337
+ const value = getValue2(obj, field.path);
1377
1338
  const options = this.model.projection.getFieldProjection(field.id).props.options;
1378
1339
  if (options) {
1379
1340
  const option = options.find((o) => o.id === value);
@@ -1383,7 +1344,7 @@ var TablePresentation = class {
1383
1344
  }
1384
1345
  }
1385
1346
  if (props.format === Format3.TypeFormat.MultiSelect) {
1386
- const values = getValue3(obj, field.path);
1347
+ const values = getValue2(obj, field.path);
1387
1348
  const options = this.model.projection.getFieldProjection(field.id).props.options;
1388
1349
  if (options && values && values.length > 0) {
1389
1350
  const tags = values.map((value) => {
@@ -1406,22 +1367,22 @@ var TablePresentation = class {
1406
1367
  }
1407
1368
  getMainGridCells(range) {
1408
1369
  const cells = {};
1409
- const fields = this.model.projection?.fields ?? [];
1370
+ const fields = this.model.projection?.getFields() ?? [];
1410
1371
  for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
1411
1372
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1412
1373
  const field = fields[col];
1413
1374
  if (!field) {
1414
1375
  continue;
1415
1376
  }
1416
- this.createDataCell(cells, this.model.rows.value[row], field, col, row);
1377
+ this.createDataCell(cells, this.model.getRows()[row], field, col, row);
1417
1378
  }
1418
1379
  }
1419
1380
  return cells;
1420
1381
  }
1421
1382
  getDraftRowCells(range) {
1422
1383
  const cells = {};
1423
- const fields = this.model.projection?.fields ?? [];
1424
- const draftRows = this.model.draftRows.value;
1384
+ const fields = this.model.projection?.getFields() ?? [];
1385
+ const draftRows = this.model.getDraftRows();
1425
1386
  if (draftRows.length === 0) {
1426
1387
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1427
1388
  cells[toPlaneCellIndex({
@@ -1461,11 +1422,11 @@ var TablePresentation = class {
1461
1422
  }
1462
1423
  getHeaderCells(range) {
1463
1424
  const cells = {};
1464
- const fields = this.model.projection?.fields ?? [];
1425
+ const fields = this.model.projection?.getFields() ?? [];
1426
+ const currentSort = this.model.getSorting();
1465
1427
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1466
1428
  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;
1429
+ const direction = currentSort?.fieldId === field.id ? currentSort.direction : void 0;
1469
1430
  cells[toPlaneCellIndex({
1470
1431
  col,
1471
1432
  row: 0
@@ -1474,7 +1435,7 @@ var TablePresentation = class {
1474
1435
  value: "",
1475
1436
  resizeHandle: "col",
1476
1437
  accessoryHtml: `
1477
- <span class="grow min-is-0 truncate">${props.title ?? field.path}</span>
1438
+ <span class="grow min-w-0 truncate">${props.title ?? field.path}</span>
1478
1439
  ${direction !== void 0 ? tableButtons.sort.render({
1479
1440
  fieldId: field.id,
1480
1441
  direction
@@ -1483,7 +1444,7 @@ var TablePresentation = class {
1483
1444
  fieldId: field.id
1484
1445
  })}
1485
1446
  `,
1486
- className: "!bg-toolbarSurface !text-description [&>div]:flex [&>div]:items-stretch"
1447
+ className: "!bg-toolbar-surface text-description! [&>div]:flex [&>div]:items-stretch"
1487
1448
  };
1488
1449
  }
1489
1450
  return cells;
@@ -1534,7 +1495,7 @@ var TablePresentation = class {
1534
1495
  col: 0,
1535
1496
  row: 0
1536
1497
  })]: {
1537
- className: "!bg-toolbarSurface",
1498
+ className: "!bg-toolbar-surface",
1538
1499
  readonly: true,
1539
1500
  value: ""
1540
1501
  }
@@ -1548,9 +1509,9 @@ var TablePresentation = class {
1548
1509
  accessoryHtml: tableControls.checkbox.render({
1549
1510
  rowIndex: 0,
1550
1511
  header: true,
1551
- checked: this.model.selection.allRowsSeleted.value
1512
+ checked: this.model.selection.allRowsSelected
1552
1513
  }),
1553
- className: "!bg-toolbarSurface",
1514
+ className: "!bg-toolbar-surface",
1554
1515
  readonly: true,
1555
1516
  value: ""
1556
1517
  }
@@ -1563,9 +1524,9 @@ var TablePresentation = class {
1563
1524
  row: 0
1564
1525
  })]: {
1565
1526
  accessoryHtml: this.model.features.schemaEditable ? tableButtons.addColumn.render({
1566
- disabled: (this.model.projection?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT
1527
+ disabled: (this.model.projection?.getFields()?.length ?? 0) >= VIEW_FIELD_LIMIT
1567
1528
  }) : void 0,
1568
- className: "!bg-toolbarSurface",
1529
+ className: "!bg-toolbar-surface",
1569
1530
  readonly: true,
1570
1531
  value: ""
1571
1532
  }
@@ -1573,7 +1534,7 @@ var TablePresentation = class {
1573
1534
  }
1574
1535
  getDraftActionCells(range) {
1575
1536
  const cells = {};
1576
- const draftRows = this.model.draftRows.value;
1537
+ const draftRows = this.model.getDraftRows();
1577
1538
  if (draftRows.length === 0) {
1578
1539
  cells[toPlaneCellIndex({
1579
1540
  col: 0,
@@ -1604,7 +1565,7 @@ var TablePresentation = class {
1604
1565
  }
1605
1566
  getDraftIconCells(range) {
1606
1567
  const cells = {};
1607
- const draftRows = this.model.draftRows.value;
1568
+ const draftRows = this.model.getDraftRows();
1608
1569
  for (let row = range.start.row; row <= range.end.row; row++) {
1609
1570
  cells[toPlaneCellIndex({
1610
1571
  col: 0,
@@ -1623,7 +1584,7 @@ var cellClassesForRowSelection = (selected, selectionMode) => {
1623
1584
  if (!selected) {
1624
1585
  if (selectionMode === "single") {
1625
1586
  return [
1626
- "!cursor-pointer"
1587
+ "cursor-pointer!"
1627
1588
  ];
1628
1589
  } else {
1629
1590
  return void 0;
@@ -1632,23 +1593,23 @@ var cellClassesForRowSelection = (selected, selectionMode) => {
1632
1593
  switch (selectionMode) {
1633
1594
  case "single":
1634
1595
  return [
1635
- "!bg-currentRelated dx-grid__cell--no-focus-unfurl hover:bg-hoverSurface !cursor-pointer"
1596
+ "dx-grid__cell--no-focus-unfurl !bg-current-surface hover:bg-hover-surface cursor-pointer!"
1636
1597
  ];
1637
1598
  case "multiple":
1638
1599
  return [
1639
- "!bg-gridCellSelected"
1600
+ "!bg-grid-cell-selected"
1640
1601
  ];
1641
1602
  }
1642
1603
  };
1643
1604
 
1644
1605
  // src/hooks/useAddRow.ts
1645
1606
  import { useCallback } from "react";
1646
- import { Obj as Obj4 } from "@dxos/echo";
1647
- var useAddRow = ({ space, schema }) => {
1607
+ import { Obj as Obj3 } from "@dxos/echo";
1608
+ var useAddRow = ({ db, schema }) => {
1648
1609
  return useCallback((data) => {
1649
- if (space && schema) {
1610
+ if (db && schema) {
1650
1611
  try {
1651
- space.db.add(Obj4.make(schema, data ?? {}));
1612
+ db.add(Obj3.make(schema, data ?? {}));
1652
1613
  return "final";
1653
1614
  } catch (error) {
1654
1615
  return "draft";
@@ -1656,129 +1617,137 @@ var useAddRow = ({ space, schema }) => {
1656
1617
  }
1657
1618
  return "final";
1658
1619
  }, [
1659
- space,
1620
+ db,
1660
1621
  schema
1661
1622
  ]);
1662
1623
  };
1663
1624
 
1664
1625
  // 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
- ]);
1626
+ import { useState } from "react";
1627
+ import { JsonSchema, Type } from "@dxos/echo";
1628
+ import { useAsyncEffect } from "@dxos/react-ui";
1629
+ import { ProjectionModel, createEchoChangeCallback as createEchoChangeCallback2 } from "@dxos/schema";
1630
+ var useProjectionModel = (schema, table, registry) => {
1631
+ const [projection, setProjection] = useState();
1632
+ useAsyncEffect(async () => {
1633
+ if (schema && table) {
1634
+ const view = await table.view.load();
1635
+ const jsonSchema = Type.isMutable(schema) ? schema.jsonSchema : JsonSchema.toJsonSchema(schema);
1636
+ const change = createEchoChangeCallback2(view, Type.isMutable(schema) ? schema : void 0);
1637
+ const projection2 = new ProjectionModel({
1638
+ registry,
1639
+ view,
1640
+ baseSchema: jsonSchema,
1641
+ change
1642
+ });
1643
+ projection2.normalizeView();
1644
+ setProjection(projection2);
1645
+ }
1646
+ }, [
1647
+ schema,
1648
+ table,
1649
+ registry
1650
+ ]);
1651
+ return projection;
1652
+ };
1678
1653
 
1679
1654
  // 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";
1655
+ import { RegistryContext } from "@effect-atom/atom-react";
1656
+ import { useContext, useEffect, useMemo, useState as useState2 } from "react";
1657
+ import { Obj as Obj4 } from "@dxos/echo";
1685
1658
  import { useSelected, useSelectionActions } from "@dxos/react-ui-attention";
1686
1659
  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);
1660
+ var useTableModel = ({ object, projection, db, rows, rowActions, features, onSelectionChanged, onRowAction, ...props }) => {
1661
+ const registry = useContext(RegistryContext);
1662
+ const selected = useSelected(object && Obj4.getDXN(object).toString(), "multi");
1663
+ const initialSelection = useMemo(() => selected, [
1664
+ object
1665
+ ]);
1666
+ const [model, setModel] = useState2();
1667
+ useEffect(() => {
1668
+ if (!object || !projection) {
1669
+ return;
1670
+ }
1671
+ let model2;
1672
+ const t = setTimeout(async () => {
1673
+ model2 = new TableModel({
1674
+ registry,
1675
+ object,
1676
+ projection,
1677
+ db,
1678
+ change: createEchoChangeCallback(object),
1679
+ features,
1680
+ rowActions,
1681
+ initialSelection,
1682
+ onRowAction,
1683
+ ...props
1750
1684
  });
1751
- return () => {
1752
- clear();
1753
- unsubscribe();
1754
- };
1755
- }, [
1756
- model,
1757
- onSelectionChanged
1758
- ]);
1759
- return model;
1760
- } finally {
1761
- _effect.f();
1762
- }
1685
+ await model2.open();
1686
+ setModel(model2);
1687
+ });
1688
+ return () => {
1689
+ clearTimeout(t);
1690
+ void model2?.close();
1691
+ };
1692
+ }, [
1693
+ registry,
1694
+ object,
1695
+ projection,
1696
+ features,
1697
+ rowActions,
1698
+ initialSelection
1699
+ ]);
1700
+ useEffect(() => {
1701
+ if (rows && model) {
1702
+ model.setRows(rows);
1703
+ }
1704
+ }, [
1705
+ model,
1706
+ rows
1707
+ ]);
1708
+ const { multiSelect, clear } = useSelectionActions([
1709
+ model?.id
1710
+ ].filter(isNonNullable));
1711
+ useEffect(() => {
1712
+ if (!model) {
1713
+ return;
1714
+ }
1715
+ const unsubscribe = registry.subscribe(model.selection.selectionAtom, () => {
1716
+ const selectedItems = [
1717
+ ...model.selection.selection
1718
+ ];
1719
+ multiSelect(selectedItems);
1720
+ onSelectionChanged?.(selectedItems);
1721
+ });
1722
+ return () => {
1723
+ clear();
1724
+ unsubscribe();
1725
+ };
1726
+ }, [
1727
+ registry,
1728
+ model,
1729
+ onSelectionChanged
1730
+ ]);
1731
+ return model;
1763
1732
  };
1764
1733
 
1765
1734
  export {
1766
- getBaseSchema,
1767
- makeDynamicTable,
1768
- touch,
1735
+ extractOrder,
1736
+ getSortValue,
1737
+ compareValues,
1769
1738
  BUTTON_IDENTIFIERS,
1770
1739
  tableButtons,
1771
1740
  CONTROL_IDENTIFIERS,
1772
1741
  CheckboxStory,
1773
1742
  SwitchStory,
1774
1743
  tableControls,
1775
- narrowSchema,
1744
+ createEchoChangeCallback,
1745
+ createDirectChangeCallback,
1776
1746
  TableModel,
1777
- ModalController,
1778
1747
  TablePresentation,
1779
1748
  cellClassesForRowSelection,
1780
1749
  useAddRow,
1781
1750
  useProjectionModel,
1782
1751
  useTableModel
1783
1752
  };
1784
- //# sourceMappingURL=chunk-CWSGS3Y2.mjs.map
1753
+ //# sourceMappingURL=chunk-GI72MQSG.mjs.map