@dxos/react-ui-table 0.7.5-main.9d26e3a → 0.7.5-main.e9bb01b

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 (70) hide show
  1. package/dist/lib/browser/{chunk-AT7O4RAD.mjs → chunk-T7CTRLHA.mjs} +2 -2
  2. package/dist/lib/browser/{chunk-AT7O4RAD.mjs.map → chunk-T7CTRLHA.mjs.map} +1 -1
  3. package/dist/lib/browser/index.mjs +446 -282
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/types/index.mjs +1 -1
  7. package/dist/lib/node/{chunk-YSBXNBDJ.cjs → chunk-XQRKYO7Q.cjs} +6 -6
  8. package/dist/lib/node/chunk-XQRKYO7Q.cjs.map +7 -0
  9. package/dist/lib/node/index.cjs +438 -268
  10. package/dist/lib/node/index.cjs.map +4 -4
  11. package/dist/lib/node/meta.json +1 -1
  12. package/dist/lib/node/types/index.cjs +3 -3
  13. package/dist/lib/node/types/index.cjs.map +1 -1
  14. package/dist/lib/node-esm/{chunk-6OH2NHUX.mjs → chunk-6HE6MLBK.mjs} +2 -2
  15. package/dist/lib/node-esm/{chunk-6OH2NHUX.mjs.map → chunk-6HE6MLBK.mjs.map} +1 -1
  16. package/dist/lib/node-esm/index.mjs +446 -282
  17. package/dist/lib/node-esm/index.mjs.map +4 -4
  18. package/dist/lib/node-esm/meta.json +1 -1
  19. package/dist/lib/node-esm/types/index.mjs +1 -1
  20. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  21. package/dist/types/src/components/Table/Table.stories.d.ts +1 -4
  22. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  23. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +18 -0
  24. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -0
  25. package/dist/types/src/components/TableToolbar/index.d.ts +2 -0
  26. package/dist/types/src/components/TableToolbar/index.d.ts.map +1 -0
  27. package/dist/types/src/components/index.d.ts +1 -1
  28. package/dist/types/src/components/index.d.ts.map +1 -1
  29. package/dist/types/src/components/testing.d.ts +1 -1
  30. package/dist/types/src/model/table-model.d.ts +11 -19
  31. package/dist/types/src/model/table-model.d.ts.map +1 -1
  32. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  33. package/dist/types/src/model/table-sorting.d.ts +53 -0
  34. package/dist/types/src/model/table-sorting.d.ts.map +1 -0
  35. package/dist/types/src/translations.d.ts +2 -1
  36. package/dist/types/src/translations.d.ts.map +1 -1
  37. package/dist/types/src/types/table.d.ts +3 -3
  38. package/dist/types/src/types/table.d.ts.map +1 -1
  39. package/dist/types/src/util/table-buttons.d.ts +15 -1
  40. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  41. package/dist/types/src/util/table.d.ts +2 -1
  42. package/dist/types/src/util/table.d.ts.map +1 -1
  43. package/dist/types/tsconfig.tsbuildinfo +1 -1
  44. package/package.json +35 -34
  45. package/src/_deprecated/schema/Schema.stories.tsx +1 -1
  46. package/src/components/Table/ColumnActionsMenu.tsx +4 -4
  47. package/src/components/Table/Table.stories.tsx +52 -40
  48. package/src/components/Table/Table.tsx +4 -0
  49. package/src/components/TableToolbar/TableToolbar.tsx +72 -0
  50. package/src/components/{Toolbar → TableToolbar}/index.ts +1 -1
  51. package/src/components/index.ts +1 -1
  52. package/src/model/table-model.test.ts +0 -5
  53. package/src/model/table-model.ts +40 -98
  54. package/src/model/table-presentation.ts +25 -4
  55. package/src/model/table-sorting.ts +215 -0
  56. package/src/playwright/smoke.spec.ts +18 -11
  57. package/src/translations.ts +2 -1
  58. package/src/types/table.ts +1 -1
  59. package/src/util/table-buttons.ts +33 -3
  60. package/src/util/table-controls.ts +2 -2
  61. package/src/util/table.ts +78 -52
  62. package/dist/lib/node/chunk-YSBXNBDJ.cjs.map +0 -7
  63. package/dist/types/src/components/Toolbar/Toolbar.d.ts +0 -21
  64. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +0 -1
  65. package/dist/types/src/components/Toolbar/common.d.ts +0 -14
  66. package/dist/types/src/components/Toolbar/common.d.ts.map +0 -1
  67. package/dist/types/src/components/Toolbar/index.d.ts +0 -2
  68. package/dist/types/src/components/Toolbar/index.d.ts.map +0 -1
  69. package/src/components/Toolbar/Toolbar.tsx +0 -87
  70. package/src/components/Toolbar/common.tsx +0 -71
@@ -2,7 +2,7 @@ import { createRequire } from 'node:module';const require = createRequire(import
2
2
  import {
3
3
  TableSchema,
4
4
  TableType
5
- } from "./chunk-6OH2NHUX.mjs";
5
+ } from "./chunk-6HE6MLBK.mjs";
6
6
 
7
7
  // packages/ui/react-ui-table/src/translations.ts
8
8
  var translationKey = "table";
@@ -18,7 +18,7 @@ var translations_default = [
18
18
  "new schema": "New schema",
19
19
  "continue label": "Continue",
20
20
  "create stack section label": "Create table",
21
- "add row": "Add row",
21
+ "add row label": "Add row",
22
22
  "create comment": "Create thread",
23
23
  "column action sort descending": "Sort Descending",
24
24
  "column action sort ascending": "Sort Ascending",
@@ -29,7 +29,8 @@ var translations_default = [
29
29
  "bulk delete row label": "Delete selected rows",
30
30
  "column deleted label": "Column deleted",
31
31
  "new column button label": "Create column",
32
- "create new object label": "Create new object \u201C{{text}}\u201D"
32
+ "create new object label": "Create new object \u201C{{text}}\u201D",
33
+ "save view label": "Save changes to view"
33
34
  }
34
35
  }
35
36
  }
@@ -37,7 +38,7 @@ var translations_default = [
37
38
 
38
39
  // packages/ui/react-ui-table/src/components/Table/Table.tsx
39
40
  import React7, { forwardRef, useCallback as useCallback4, useImperativeHandle, useState as useState2, useMemo as useMemo4 } from "react";
40
- import { getValue as getValue3 } from "@dxos/echo-schema";
41
+ import { getValue as getValue4 } from "@dxos/echo-schema";
41
42
  import { invariant as invariant3 } from "@dxos/invariant";
42
43
  import { Filter, getSpace as getSpace4, fullyQualifiedId } from "@dxos/react-client/echo";
43
44
  import { useTranslation as useTranslation3 } from "@dxos/react-ui";
@@ -55,7 +56,7 @@ var ColumnActionsMenu = ({ model, modals }) => {
55
56
  if (state?.type !== "column") {
56
57
  return null;
57
58
  }
58
- const currentSort = model.sorting;
59
+ const currentSort = model.sorting?.sorting;
59
60
  const isCurrentColumnSorted = currentSort?.fieldId === state.fieldId;
60
61
  return /* @__PURE__ */ React.createElement(DropdownMenu.Root, {
61
62
  modal: false,
@@ -65,13 +66,13 @@ var ColumnActionsMenu = ({ model, modals }) => {
65
66
  virtualRef: modals.trigger
66
67
  }), /* @__PURE__ */ React.createElement(DropdownMenu.Portal, null, /* @__PURE__ */ React.createElement(DropdownMenu.Content, null, /* @__PURE__ */ React.createElement(DropdownMenu.Viewport, null, (!isCurrentColumnSorted || currentSort?.direction === "asc") && /* @__PURE__ */ React.createElement(DropdownMenu.Item, {
67
68
  "data-testid": "column-sort-descending",
68
- onClick: () => model.setSort(state.fieldId, "desc")
69
+ onClick: () => model.sorting?.setSort(state.fieldId, "desc")
69
70
  }, t("column action sort descending")), (!isCurrentColumnSorted || currentSort?.direction === "desc") && /* @__PURE__ */ React.createElement(DropdownMenu.Item, {
70
71
  "data-testid": "column-sort-ascending",
71
- onClick: () => model.setSort(state.fieldId, "asc")
72
+ onClick: () => model.sorting?.setSort(state.fieldId, "asc")
72
73
  }, t("column action sort ascending")), isCurrentColumnSorted && /* @__PURE__ */ React.createElement(DropdownMenu.Item, {
73
74
  "data-testid": "column-clear-sort",
74
- onClick: () => model.clearSort()
75
+ onClick: () => model.sorting?.clearSort()
75
76
  }, t("column action clear sorting")), model.getColumnCount() > 1 && /* @__PURE__ */ React.createElement(DropdownMenu.Item, {
76
77
  "data-testid": "column-delete",
77
78
  onClick: () => model.deleteColumn(state.fieldId)
@@ -286,14 +287,13 @@ var RowActionsMenu = ({ model, modals }) => {
286
287
  };
287
288
 
288
289
  // packages/ui/react-ui-table/src/model/table-model.ts
289
- import { computed as computed2, effect as effect2, signal as signal2 } from "@preact/signals-core";
290
- import orderBy from "lodash.orderby";
290
+ import { computed as computed3, effect as effect2, signal as signal3 } from "@preact/signals-core";
291
291
  import { Resource as Resource2 } from "@dxos/context";
292
- import { getValue, setValue, FormatEnum as FormatEnum2 } from "@dxos/echo-schema";
292
+ import { FormatEnum as FormatEnum3, getValue as getValue2, setValue } from "@dxos/echo-schema";
293
293
  import { invariant } from "@dxos/invariant";
294
294
  import { isReactiveObject, makeRef as makeRef2 } from "@dxos/live-object";
295
295
  import { PublicKey as PublicKey2 } from "@dxos/react-client";
296
- import { formatForDisplay, formatForEditing, parseValue } from "@dxos/react-ui-form";
296
+ import { formatForEditing, parseValue } from "@dxos/react-ui-form";
297
297
 
298
298
  // packages/ui/react-ui-table/src/model/selection-model.ts
299
299
  import { computed, effect, signal } from "@preact/signals-core";
@@ -304,73 +304,81 @@ var touch = (value) => value;
304
304
 
305
305
  // packages/ui/react-ui-table/src/util/table.ts
306
306
  import { AST, Format, FormatEnum, S as S2, TypedObject, TypeEnum } from "@dxos/echo-schema";
307
- import { log } from "@dxos/log";
308
307
  import { PublicKey } from "@dxos/react-client";
309
308
  import { create as create2, makeRef } from "@dxos/react-client/echo";
310
- import { createFieldId, createView, ViewProjection } from "@dxos/schema";
311
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/util/table.ts";
312
- var initializeTable = async ({ space, table, initialRow = true }) => {
313
- log("initializeTable", {
314
- table
315
- }, {
316
- F: __dxlog_file,
317
- L: 36,
318
- S: void 0,
319
- C: (f, a) => f(...a)
320
- });
321
- const ContactSchema = TypedObject({
322
- typename: `example.com/type/${PublicKey.random().truncate()}`,
323
- version: "0.1.0"
324
- })({
325
- name: S2.optional(S2.String).annotations({
326
- [AST.TitleAnnotationId]: "Name"
327
- }),
328
- active: S2.optional(S2.Boolean),
329
- email: S2.optional(Format.Email),
330
- salary: S2.optional(Format.Currency()).annotations({
331
- [AST.TitleAnnotationId]: "Salary"
332
- })
333
- });
334
- const [contactSchema] = await space.db.schemaRegistry.register([
335
- ContactSchema
336
- ]);
337
- table.view = makeRef(createView({
338
- name: "Test",
339
- typename: contactSchema.typename,
340
- jsonSchema: contactSchema.jsonSchema,
341
- fields: [
342
- "name",
343
- "active",
344
- "email",
345
- "salary"
346
- ]
347
- }));
348
- const projection = new ViewProjection(contactSchema, table.view.target);
349
- projection.setFieldProjection({
350
- field: {
351
- id: table.view.target.fields.find((f) => f.path === "salary").id,
352
- path: "salary",
353
- size: 150
309
+ import { createFieldId, createView, getSchemaProperties, ViewProjection } from "@dxos/schema";
310
+ var initializeTable = async ({ space, table, initialRow = true, initialSchema }) => {
311
+ if (initialSchema) {
312
+ const schema = await space.db.schemaRegistry.query({
313
+ typename: initialSchema
314
+ }).firstOrUndefined();
315
+ if (!schema) {
316
+ throw new Error(`Schema not found: ${initialSchema}`);
354
317
  }
355
- });
356
- projection.setFieldProjection({
357
- field: {
358
- id: createFieldId(),
359
- path: "manager",
360
- referencePath: "name"
361
- },
362
- props: {
363
- property: "manager",
364
- type: TypeEnum.Ref,
365
- format: FormatEnum.Ref,
366
- referenceSchema: contactSchema.typename,
367
- title: "Manager"
318
+ const fields = getSchemaProperties(schema.ast).filter((prop) => prop.type !== "object" || prop.format === FormatEnum.Ref).map((prop) => prop.name);
319
+ table.view = makeRef(createView({
320
+ // TODO(ZaymonFC): Don't hardcode name?
321
+ name: "View",
322
+ typename: schema.typename,
323
+ jsonSchema: schema.jsonSchema,
324
+ fields
325
+ }));
326
+ return schema;
327
+ } else {
328
+ const ContactSchema = TypedObject({
329
+ typename: `example.com/type/${PublicKey.random().truncate()}`,
330
+ version: "0.1.0"
331
+ })({
332
+ name: S2.optional(S2.String).annotations({
333
+ [AST.TitleAnnotationId]: "Name"
334
+ }),
335
+ active: S2.optional(S2.Boolean),
336
+ email: S2.optional(Format.Email),
337
+ salary: S2.optional(Format.Currency()).annotations({
338
+ [AST.TitleAnnotationId]: "Salary"
339
+ })
340
+ });
341
+ const [contactSchema] = await space.db.schemaRegistry.register([
342
+ ContactSchema
343
+ ]);
344
+ table.view = makeRef(createView({
345
+ name: "View",
346
+ typename: contactSchema.typename,
347
+ jsonSchema: contactSchema.jsonSchema,
348
+ fields: [
349
+ "name",
350
+ "active",
351
+ "email",
352
+ "salary"
353
+ ]
354
+ }));
355
+ const projection = new ViewProjection(contactSchema, table.view.target);
356
+ projection.setFieldProjection({
357
+ field: {
358
+ id: table.view.target.fields.find((f) => f.path === "salary").id,
359
+ path: "salary",
360
+ size: 150
361
+ }
362
+ });
363
+ projection.setFieldProjection({
364
+ field: {
365
+ id: createFieldId(),
366
+ path: "manager",
367
+ referencePath: "name"
368
+ },
369
+ props: {
370
+ property: "manager",
371
+ type: TypeEnum.Ref,
372
+ format: FormatEnum.Ref,
373
+ referenceSchema: contactSchema.typename,
374
+ title: "Manager"
375
+ }
376
+ });
377
+ if (initialRow) {
378
+ space.db.add(create2(contactSchema, {}));
368
379
  }
369
- });
370
- if (initialRow) {
371
- space.db.add(create2(contactSchema, {}));
380
+ return contactSchema;
372
381
  }
373
- return contactSchema;
374
382
  };
375
383
 
376
384
  // packages/ui/react-ui-table/src/util/table-buttons.ts
@@ -378,11 +386,13 @@ var BUTTON_IDENTIFIERS = {
378
386
  columnSettings: "data-table-column-settings-button",
379
387
  newColumn: "data-table-new-column-button",
380
388
  referencedCell: "data-table-ref-cell-button",
381
- rowMenu: "data-table-row-menu-button"
389
+ rowMenu: "data-table-row-menu-button",
390
+ sort: "data-table-sort-button"
382
391
  };
383
- var createButton = ({ attr, data, disabled = false, icon, testId }) => {
392
+ var createButton = ({ attr, data, disabled = false, icon, testId, type = "primary" }) => {
384
393
  const dataAttrs = Object.entries(data).map(([k, v]) => `${k}="${v}"`).join(" ");
385
- return `<button ${attr} data-testid="${testId}" class="ch-button is-6 pli-0.5 min-bs-0 absolute inset-block-1 inline-end-2" ${dataAttrs} ${disabled ? "disabled" : ""}><svg><use href="/icons.svg#${icon}"/></svg></button>`;
394
+ const positionClass = type === "primary" ? "inline-end-2" : "inline-end-9";
395
+ return `<button ${attr} data-testid="${testId}" class="dx-button is-6 pli-0.5 min-bs-0 absolute inset-block-1 ${positionClass}" ${dataAttrs} ${disabled ? "disabled" : ""}><svg><use href="/icons.svg#${icon}"/></svg></button>`;
386
396
  };
387
397
  var addColumnButton = {
388
398
  attr: BUTTON_IDENTIFIERS.newColumn,
@@ -456,11 +466,35 @@ var rowMenuButton = {
456
466
  rowIndex: Number(el.getAttribute("data-row-index"))
457
467
  })
458
468
  };
469
+ var sortButton = {
470
+ attr: BUTTON_IDENTIFIERS.sort,
471
+ icon: "ph--sort-ascending--regular",
472
+ render: ({ fieldId, direction }) => {
473
+ return createButton({
474
+ attr: BUTTON_IDENTIFIERS.sort,
475
+ icon: direction === "desc" ? "ph--sort-descending--regular" : "ph--sort-ascending--regular",
476
+ data: {
477
+ "data-field-id": fieldId,
478
+ "data-direction": direction ?? ""
479
+ },
480
+ testId: "table-sort-button",
481
+ // TODO(ZaymonFC): All buttons should be rendered into an absolutely positioned flex container.
482
+ // This is a bit hacky.
483
+ type: "secondary"
484
+ });
485
+ },
486
+ getData: (el) => ({
487
+ type: "sort",
488
+ fieldId: el.getAttribute("data-field-id"),
489
+ direction: el.getAttribute("data-direction")
490
+ })
491
+ };
459
492
  var tableButtons = {
460
493
  addColumn: addColumnButton,
461
494
  columnSettings: columnSettingsButton,
462
495
  referencedCell: referencedCellButton,
463
- rowMenu: rowMenuButton
496
+ rowMenu: rowMenuButton,
497
+ sort: sortButton
464
498
  };
465
499
 
466
500
  // packages/ui/react-ui-table/src/util/table-controls.ts
@@ -469,8 +503,8 @@ var CONTROL_IDENTIFIERS = {
469
503
  switch: "data-table-switch"
470
504
  };
471
505
  var BASE_CLASSES = {
472
- checkbox: "absolute inset-block-[.375rem] inline-end-[.375rem] ch-checkbox",
473
- switch: "absolute inset-block-[.25rem] inline-end-[.25rem] ch-checkbox--switch"
506
+ checkbox: "absolute inset-block-[.375rem] inline-end-[.375rem] dx-checkbox",
507
+ switch: "absolute inset-block-[.25rem] inline-end-[.25rem] dx-checkbox--switch"
474
508
  };
475
509
  var renderAttributes = (data) => {
476
510
  return Object.entries(data).map(([k, v]) => `${k}="${v}"`).join(" ");
@@ -597,8 +631,173 @@ var SelectionModel = class extends Resource {
597
631
  }
598
632
  };
599
633
 
634
+ // packages/ui/react-ui-table/src/model/table-sorting.ts
635
+ import { computed as computed2, signal as signal2 } from "@preact/signals-core";
636
+ import orderBy from "lodash.orderby";
637
+ import { getValue, FormatEnum as FormatEnum2, TypeEnum as TypeEnum2 } from "@dxos/echo-schema";
638
+ import { formatForDisplay } from "@dxos/react-ui-form";
639
+ var TableSorting = class {
640
+ constructor(rows, _view, _projection) {
641
+ this._view = _view;
642
+ this._projection = _projection;
643
+ this._displayToDataIndex = /* @__PURE__ */ new Map();
644
+ this._localSort = signal2(void 0);
645
+ this._rows = rows;
646
+ this._isDirty = computed2(() => {
647
+ const local = this._localSort.value;
648
+ const viewSort = this._view?.query.sort?.[0];
649
+ if (local?.type === "cleared") {
650
+ return viewSort !== void 0;
651
+ }
652
+ if (local?.type === "active") {
653
+ if (!viewSort) {
654
+ return true;
655
+ }
656
+ return local.sort.fieldId !== viewSort.fieldId || local.sort.direction !== viewSort.direction;
657
+ }
658
+ return false;
659
+ });
660
+ this.sortedRows = this.initialiseSortedRows();
661
+ }
662
+ /**
663
+ * @reactive
664
+ * @returns Local sort if present, falls back to view sort.
665
+ */
666
+ get sorting() {
667
+ const local = this._localSort.value;
668
+ if (local?.type === "cleared") {
669
+ return void 0;
670
+ }
671
+ if (local?.type === "active") {
672
+ return local.sort;
673
+ }
674
+ return this._view?.query.sort?.[0];
675
+ }
676
+ /**
677
+ * @reactive
678
+ * @returns Whether local sort differs from view sort.
679
+ */
680
+ get isDirty() {
681
+ return this._isDirty.value;
682
+ }
683
+ getDataIndex(displayIndex) {
684
+ return this._displayToDataIndex.get(displayIndex) ?? displayIndex;
685
+ }
686
+ setSort(fieldId, direction) {
687
+ this._localSort.value = {
688
+ type: "active",
689
+ sort: {
690
+ fieldId,
691
+ direction
692
+ }
693
+ };
694
+ }
695
+ toggleSort(fieldId) {
696
+ if (!this._view || !this.sorting || this.sorting.fieldId !== fieldId) {
697
+ return;
698
+ }
699
+ switch (this.sorting.direction) {
700
+ case "asc": {
701
+ this.setSort(fieldId, "desc");
702
+ break;
703
+ }
704
+ case "desc": {
705
+ this.setSort(fieldId, "asc");
706
+ break;
707
+ }
708
+ }
709
+ }
710
+ clearSort() {
711
+ this._localSort.value = {
712
+ type: "cleared"
713
+ };
714
+ }
715
+ save() {
716
+ if (this._view && this._localSort.value !== void 0) {
717
+ if (this._localSort.value.type === "active") {
718
+ this._view.query.sort = [
719
+ this._localSort.value.sort
720
+ ];
721
+ } else {
722
+ this._view.query.sort = [];
723
+ }
724
+ this._localSort.value = void 0;
725
+ }
726
+ }
727
+ //
728
+ // Initialisation.
729
+ //
730
+ initialiseSortedRows() {
731
+ return computed2(() => {
732
+ this._displayToDataIndex.clear();
733
+ const sort = this.sorting;
734
+ if (!sort || !this._view) {
735
+ return this._rows.value;
736
+ }
737
+ const field = this._view.fields.find((f) => f.id === sort.fieldId);
738
+ if (!field) {
739
+ return this._rows.value;
740
+ }
741
+ const { props } = this._projection.getFieldProjection(field.id);
742
+ const dataWithIndices = this._rows.value.map((item, index) => {
743
+ const sortValue = this.getSortValue(props, field, item);
744
+ const isEmpty = !(props.type === TypeEnum2.Boolean) && (sortValue == null || sortValue === "");
745
+ return {
746
+ item,
747
+ index,
748
+ value: sortValue,
749
+ isEmpty
750
+ };
751
+ });
752
+ const ordered = orderBy(dataWithIndices, [
753
+ "isEmpty",
754
+ "value"
755
+ ], [
756
+ "asc",
757
+ sort.direction
758
+ ]);
759
+ ordered.forEach(({ index: dataIndex }, displayIndex) => {
760
+ if (displayIndex !== dataIndex) {
761
+ this._displayToDataIndex.set(displayIndex, dataIndex);
762
+ }
763
+ });
764
+ return ordered.map(({ item }) => item);
765
+ });
766
+ }
767
+ getSortValue(props, field, item) {
768
+ const { type, format } = props;
769
+ const value = getValue(item, field.path);
770
+ if (type === TypeEnum2.Boolean) {
771
+ return !!value;
772
+ }
773
+ if (type === TypeEnum2.Number) {
774
+ return value;
775
+ }
776
+ if (value == null) {
777
+ return value;
778
+ }
779
+ if (format === FormatEnum2.SingleSelect) {
780
+ const options = props.options;
781
+ if (!options) {
782
+ return void 0;
783
+ }
784
+ const option = options.find((option2) => option2.id === value);
785
+ return option ? options.indexOf(option) : void 0;
786
+ }
787
+ if (format === FormatEnum2.Ref && field.referencePath) {
788
+ const refValue = getValue(value.target, field.referencePath);
789
+ return refValue;
790
+ }
791
+ return formatForDisplay({
792
+ type,
793
+ format,
794
+ value
795
+ });
796
+ }
797
+ };
798
+
600
799
  // packages/ui/react-ui-table/src/model/table-model.ts
601
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/model/table-model.ts";
800
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/model/table-model.ts";
602
801
  var TableModel = class extends Resource2 {
603
802
  constructor({ table, projection, sorting = [], pinnedRows = {
604
803
  top: [],
@@ -606,7 +805,7 @@ var TableModel = class extends Resource2 {
606
805
  }, onCellUpdate, onDeleteColumn, onDeleteRows, onInsertRow, onRowOrderChanged }) {
607
806
  super();
608
807
  this.id = `table-model-${PublicKey2.random().truncate()}`;
609
- this._visibleRange = signal2({
808
+ this._visibleRange = signal3({
610
809
  start: {
611
810
  row: 0,
612
811
  col: 0
@@ -616,14 +815,7 @@ var TableModel = class extends Resource2 {
616
815
  col: 0
617
816
  }
618
817
  });
619
- /**
620
- * Maps display indices to data indices.
621
- * Used for translating between sorted/displayed order and original data order.
622
- * Keys are display indices, values are corresponding data indices.
623
- */
624
- this._displayToDataIndex = /* @__PURE__ */ new Map();
625
- this._sorting = signal2(void 0);
626
- this._rows = signal2([]);
818
+ this._rows = signal3([]);
627
819
  //
628
820
  // Data
629
821
  //
@@ -633,11 +825,11 @@ var TableModel = class extends Resource2 {
633
825
  this.getRowCount = () => this._rows.value.length;
634
826
  this.getColumnCount = () => this.table.view?.target?.fields.length ?? 0;
635
827
  this.insertRow = (rowIndex) => {
636
- const row = rowIndex !== void 0 ? this._displayToDataIndex.get(rowIndex) ?? rowIndex : this._rows.value.length;
828
+ const row = rowIndex !== void 0 ? this._sorting.getDataIndex(rowIndex) : this._rows.value.length;
637
829
  this._onInsertRow?.(row);
638
830
  };
639
831
  this.deleteRow = (rowIndex) => {
640
- const row = this._displayToDataIndex.get(rowIndex) ?? rowIndex;
832
+ const row = this._sorting.getDataIndex(rowIndex);
641
833
  const obj = this._rows.value[row];
642
834
  const objectsToDelete = [];
643
835
  if (this._selection.hasSelection.value) {
@@ -654,18 +846,18 @@ var TableModel = class extends Resource2 {
654
846
  return void 0;
655
847
  }
656
848
  const field = fields[col];
657
- const dataIndex = this._displayToDataIndex.get(row) ?? row;
658
- const value = getValue(this._rows.value[dataIndex], field.path);
849
+ const dataIndex = this._sorting.getDataIndex(row);
850
+ const value = getValue2(this._rows.value[dataIndex], field.path);
659
851
  if (value == null) {
660
852
  return "";
661
853
  }
662
854
  const { props } = this._projection.getFieldProjection(field.id);
663
855
  switch (props.format) {
664
- case FormatEnum2.Ref: {
856
+ case FormatEnum3.Ref: {
665
857
  if (!field.referencePath) {
666
858
  return "";
667
859
  }
668
- return getValue(value.target, field.referencePath);
860
+ return getValue2(value.target, field.referencePath);
669
861
  }
670
862
  default: {
671
863
  return formatForEditing({
@@ -677,7 +869,7 @@ var TableModel = class extends Resource2 {
677
869
  }
678
870
  };
679
871
  this.setCellData = ({ col, row }, value) => {
680
- const rowIdx = this._displayToDataIndex.get(row) ?? row;
872
+ const rowIdx = this._sorting.getDataIndex(row);
681
873
  const fields = this.table.view?.target?.fields ?? [];
682
874
  if (col < 0 || col >= fields.length) {
683
875
  return;
@@ -685,7 +877,7 @@ var TableModel = class extends Resource2 {
685
877
  const field = fields[col];
686
878
  const { props } = this._projection.getFieldProjection(field.id);
687
879
  switch (props.format) {
688
- case FormatEnum2.Ref: {
880
+ case FormatEnum3.Ref: {
689
881
  if (isReactiveObject(value)) {
690
882
  setValue(this._rows.value[rowIdx], field.path, makeRef2(value));
691
883
  }
@@ -703,7 +895,11 @@ var TableModel = class extends Resource2 {
703
895
  };
704
896
  this._table = table;
705
897
  this._projection = projection;
706
- this._sorting.value = sorting.at(0);
898
+ this._sorting = new TableSorting(this._rows, table.view?.target, projection);
899
+ if (sorting.length > 0) {
900
+ const [sort] = sorting;
901
+ this._sorting.setSort(sort.fieldId, sort.direction);
902
+ }
707
903
  this._pinnedRows = pinnedRows;
708
904
  this._onInsertRow = onInsertRow;
709
905
  this._onDeleteRows = onDeleteRows;
@@ -718,14 +914,20 @@ var TableModel = class extends Resource2 {
718
914
  return this._projection;
719
915
  }
720
916
  get rows() {
721
- return this._sortedRows;
917
+ return this._sorting.sortedRows;
918
+ }
919
+ /**
920
+ * @reactive
921
+ */
922
+ get sorting() {
923
+ return this._sorting;
722
924
  }
723
925
  get pinnedRows() {
724
926
  return this._pinnedRows;
725
927
  }
726
928
  get columnMeta() {
727
929
  invariant(this._columnMeta, void 0, {
728
- F: __dxlog_file2,
930
+ F: __dxlog_file,
729
931
  L: 117,
730
932
  S: this,
731
933
  A: [
@@ -735,24 +937,24 @@ var TableModel = class extends Resource2 {
735
937
  });
736
938
  return this._columnMeta;
737
939
  }
738
- get sorting() {
739
- return this._sorting.value;
740
- }
741
940
  get selection() {
742
941
  return this._selection;
743
942
  }
943
+ /** @reactive */
944
+ get isViewDirty() {
945
+ return this._sorting.isDirty;
946
+ }
744
947
  //
745
948
  // Initialisation
746
949
  //
747
950
  async _open() {
748
951
  this.initializeColumnMeta();
749
- this.initializeSorting();
750
952
  this.initializeEffects();
751
- this._selection = new SelectionModel(this._sortedRows, () => this._onRowOrderChanged?.());
953
+ this._selection = new SelectionModel(this._sorting.sortedRows, () => this._onRowOrderChanged?.());
752
954
  await this._selection.open(this._ctx);
753
955
  }
754
956
  initializeColumnMeta() {
755
- this._columnMeta = computed2(() => {
957
+ this._columnMeta = computed3(() => {
756
958
  const fields = this._table.view?.target?.fields ?? [];
757
959
  const meta = Object.fromEntries(fields.map((field, index) => [
758
960
  index,
@@ -778,60 +980,9 @@ var TableModel = class extends Resource2 {
778
980
  };
779
981
  });
780
982
  }
781
- initializeSorting() {
782
- this._sortedRows = computed2(() => {
783
- this._displayToDataIndex.clear();
784
- const sort = this._sorting.value;
785
- if (!sort) {
786
- return this._rows.value;
787
- }
788
- const field = this._table.view?.target?.fields.find((field2) => field2.id === sort.fieldId);
789
- if (!field) {
790
- return this._rows.value;
791
- }
792
- const { props } = this._projection.getFieldProjection(field.id);
793
- const dataWithIndices = this._rows.value.map((item, index) => {
794
- const rawValue = getValue(item, field.path);
795
- let sortValue = rawValue;
796
- if (props.type.startsWith("boolean")) {
797
- sortValue = !!rawValue;
798
- } else if (rawValue != null) {
799
- if (props.format === FormatEnum2.Ref && field.referencePath) {
800
- sortValue = getValue(rawValue, field.referencePath);
801
- } else {
802
- sortValue = formatForDisplay({
803
- type: props.type,
804
- format: props.format,
805
- value: rawValue
806
- });
807
- }
808
- }
809
- return {
810
- item,
811
- index,
812
- value: sortValue,
813
- isEmpty: !props.type.startsWith("boolean") && (sortValue == null || sortValue === "")
814
- };
815
- });
816
- const sorted = orderBy(dataWithIndices, [
817
- "isEmpty",
818
- "value"
819
- ], [
820
- "asc",
821
- sort.direction
822
- ]);
823
- for (let displayIndex = 0; displayIndex < sorted.length; displayIndex++) {
824
- const { index: dataIndex } = sorted[displayIndex];
825
- if (displayIndex !== dataIndex) {
826
- this._displayToDataIndex.set(displayIndex, dataIndex);
827
- }
828
- }
829
- return sorted.map(({ item }) => item);
830
- });
831
- }
832
983
  initializeEffects() {
833
984
  const rowOrderWatcher = effect2(() => {
834
- touch(this._sortedRows.value);
985
+ touch(this._sorting.sortedRows.value);
835
986
  this._onRowOrderChanged?.();
836
987
  });
837
988
  this._ctx.onDispose(rowOrderWatcher);
@@ -840,8 +991,8 @@ var TableModel = class extends Resource2 {
840
991
  const rowEffects = [];
841
992
  for (let row = start.row; row <= end.row; row++) {
842
993
  rowEffects.push(effect2(() => {
843
- const obj = this._sortedRows.value[row];
844
- this?._table?.view?.target?.fields.forEach((field) => touch(getValue(obj, field.path)));
994
+ const obj = this._sorting.sortedRows.value[row];
995
+ this?._table?.view?.target?.fields.forEach((field) => touch(getValue2(obj, field.path)));
845
996
  this._onCellUpdate?.({
846
997
  row,
847
998
  col: start.col,
@@ -859,10 +1010,10 @@ var TableModel = class extends Resource2 {
859
1010
  * @param {(value: any) => any} update - A function that takes the current value and returns the updated value.
860
1011
  */
861
1012
  updateCellData({ col, row }, update) {
862
- const dataRow = this._displayToDataIndex.get(row) ?? row;
1013
+ const dataRow = this._sorting.getDataIndex(row);
863
1014
  const fields = this.table.view?.target?.fields ?? [];
864
1015
  const field = fields[col];
865
- const value = getValue(this._rows.value[dataRow], field.path);
1016
+ const value = getValue2(this._rows.value[dataRow], field.path);
866
1017
  const updatedValue = update(value);
867
1018
  setValue(this._rows.value[dataRow], field.path, updatedValue);
868
1019
  }
@@ -875,9 +1026,6 @@ var TableModel = class extends Resource2 {
875
1026
  }
876
1027
  const field = this.table.view.target?.fields.find((field2) => field2.id === fieldId);
877
1028
  if (field && this._onDeleteColumn) {
878
- if (this._sorting.value?.fieldId === fieldId) {
879
- this.clearSort();
880
- }
881
1029
  this._onDeleteColumn(field.id);
882
1030
  }
883
1031
  }
@@ -895,18 +1043,6 @@ var TableModel = class extends Resource2 {
895
1043
  }
896
1044
  }
897
1045
  //
898
- // Sorting
899
- //
900
- setSort(fieldId, direction) {
901
- this._sorting.value = {
902
- fieldId,
903
- direction
904
- };
905
- }
906
- clearSort() {
907
- this._sorting.value = void 0;
908
- }
909
- //
910
1046
  // Pinning
911
1047
  //
912
1048
  // TODO(burdon): Change to setPinned(on/off).
@@ -917,15 +1053,21 @@ var TableModel = class extends Resource2 {
917
1053
  this._pinnedRows.top = this._pinnedRows.top.filter((index) => index !== rowIndex);
918
1054
  this._pinnedRows.bottom = this._pinnedRows.bottom.filter((index) => index !== rowIndex);
919
1055
  }
1056
+ //
1057
+ // View operations
1058
+ //
1059
+ saveView() {
1060
+ this._sorting.save();
1061
+ }
920
1062
  };
921
1063
 
922
1064
  // packages/ui/react-ui-table/src/model/modal-controller.ts
923
- import { signal as signal3 } from "@preact/signals-core";
924
- import { log as log2 } from "@dxos/log";
925
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/model/modal-controller.ts";
1065
+ import { signal as signal4 } from "@preact/signals-core";
1066
+ import { log } from "@dxos/log";
1067
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/model/modal-controller.ts";
926
1068
  var ModalController = class {
927
1069
  constructor() {
928
- this._state = signal3({
1070
+ this._state = signal4({
929
1071
  type: "closed"
930
1072
  });
931
1073
  this._triggerRef = {
@@ -985,8 +1127,8 @@ var ModalController = class {
985
1127
  onCreate
986
1128
  };
987
1129
  } else {
988
- log2.warn("anchor cell not found while creating new ref", void 0, {
989
- F: __dxlog_file3,
1130
+ log.warn("anchor cell not found while creating new ref", void 0, {
1131
+ F: __dxlog_file2,
990
1132
  L: 86,
991
1133
  S: this,
992
1134
  C: (f, a) => f(...a)
@@ -1008,14 +1150,14 @@ var ModalController = class {
1008
1150
  };
1009
1151
 
1010
1152
  // packages/ui/react-ui-table/src/model/table-presentation.ts
1011
- import { signal as signal4 } from "@preact/signals-core";
1012
- import { FormatEnum as FormatEnum3, getValue as getValue2 } from "@dxos/echo-schema";
1153
+ import { signal as signal5 } from "@preact/signals-core";
1154
+ import { FormatEnum as FormatEnum4, getValue as getValue3 } from "@dxos/echo-schema";
1013
1155
  import { cellClassesForFieldType, cellClassesForRowSelection, formatForDisplay as formatForDisplay2 } from "@dxos/react-ui-form";
1014
1156
  import { toPlaneCellIndex } from "@dxos/react-ui-grid";
1015
1157
  import { mx } from "@dxos/react-ui-theme";
1016
1158
  import { VIEW_FIELD_LIMIT } from "@dxos/schema";
1017
1159
  var TablePresentation = class {
1018
- constructor(model, _visibleRange = signal4({
1160
+ constructor(model, _visibleRange = signal5({
1019
1161
  start: {
1020
1162
  row: 0,
1021
1163
  col: 0
@@ -1054,19 +1196,20 @@ var TablePresentation = class {
1054
1196
  const { props } = this.model.projection.getFieldProjection(field.id);
1055
1197
  const cell = {
1056
1198
  get value() {
1057
- const value = getValue2(obj, field.path);
1199
+ const value = getValue3(obj, field.path);
1058
1200
  if (value == null) {
1059
1201
  return "";
1060
1202
  }
1061
1203
  switch (props.format) {
1062
- case FormatEnum3.Boolean: {
1204
+ case FormatEnum4.Boolean:
1205
+ case FormatEnum4.SingleSelect: {
1063
1206
  return "";
1064
1207
  }
1065
- case FormatEnum3.Ref: {
1208
+ case FormatEnum4.Ref: {
1066
1209
  if (!field.referencePath) {
1067
1210
  return "";
1068
1211
  }
1069
- return getValue2(value.target, field.referencePath);
1212
+ return getValue3(value.target, field.referencePath);
1070
1213
  }
1071
1214
  default: {
1072
1215
  return formatForDisplay2({
@@ -1093,8 +1236,8 @@ var TablePresentation = class {
1093
1236
  if (classes.length > 0) {
1094
1237
  cell.className = mx(classes.flat());
1095
1238
  }
1096
- if (props.format === FormatEnum3.Ref && props.referenceSchema) {
1097
- const targetObj = getValue2(obj, field.path)?.target;
1239
+ if (props.format === FormatEnum4.Ref && props.referenceSchema) {
1240
+ const targetObj = getValue3(obj, field.path)?.target;
1098
1241
  if (targetObj) {
1099
1242
  cell.accessoryHtml = tableButtons.referencedCell.render({
1100
1243
  targetId: targetObj.id,
@@ -1102,8 +1245,8 @@ var TablePresentation = class {
1102
1245
  });
1103
1246
  }
1104
1247
  }
1105
- if (props.format === FormatEnum3.Boolean) {
1106
- const value = getValue2(obj, field.path);
1248
+ if (props.format === FormatEnum4.Boolean) {
1249
+ const value = getValue3(obj, field.path);
1107
1250
  cell.accessoryHtml = tableControls.switch.render({
1108
1251
  colIndex,
1109
1252
  rowIndex: displayIndex,
@@ -1111,6 +1254,16 @@ var TablePresentation = class {
1111
1254
  });
1112
1255
  cell.readonly = true;
1113
1256
  }
1257
+ if (props.format === FormatEnum4.SingleSelect) {
1258
+ const value = getValue3(obj, field.path);
1259
+ const options = this.model.projection.getFieldProjection(field.id).props.options;
1260
+ if (options) {
1261
+ const option = options.find((o) => o.id === value);
1262
+ if (option) {
1263
+ cell.accessoryHtml = `<span class="dx-tag" data-hue="${option.color}">${option.title}</span>`;
1264
+ }
1265
+ }
1266
+ }
1114
1267
  const idx = toPlaneCellIndex({
1115
1268
  col: colIndex,
1116
1269
  row: displayIndex
@@ -1133,6 +1286,8 @@ var TablePresentation = class {
1133
1286
  const fields = this.model.table.view?.target?.fields ?? [];
1134
1287
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1135
1288
  const { field, props } = this.model.projection.getFieldProjection(fields[col].id);
1289
+ const sorting = this.model.sorting?.sorting;
1290
+ const direction = sorting?.fieldId === field.id ? sorting.direction : void 0;
1136
1291
  cells[toPlaneCellIndex({
1137
1292
  col,
1138
1293
  row: 0
@@ -1141,9 +1296,16 @@ var TablePresentation = class {
1141
1296
  value: props.title ?? field.path,
1142
1297
  readonly: true,
1143
1298
  resizeHandle: "col",
1144
- accessoryHtml: tableButtons.columnSettings.render({
1299
+ accessoryHtml: `
1300
+ ${direction !== void 0 ? tableButtons.sort.render({
1301
+ fieldId: field.id,
1302
+ direction
1303
+ }) : ""}
1304
+ ${tableButtons.columnSettings.render({
1145
1305
  fieldId: field.id
1146
- })
1306
+ })}
1307
+ `,
1308
+ className: "!bg-gridHeader"
1147
1309
  };
1148
1310
  }
1149
1311
  return cells;
@@ -1193,13 +1355,14 @@ var TablePresentation = class {
1193
1355
  col: 0,
1194
1356
  row: 0
1195
1357
  })]: {
1196
- value: "",
1197
1358
  accessoryHtml: tableControls.checkbox.render({
1198
1359
  rowIndex: 0,
1199
1360
  header: true,
1200
1361
  checked: this.model.selection.allRowsSeleted.value
1201
1362
  }),
1202
- readonly: true
1363
+ className: "!bg-gridHeader",
1364
+ readonly: true,
1365
+ value: ""
1203
1366
  }
1204
1367
  };
1205
1368
  }
@@ -1209,11 +1372,12 @@ var TablePresentation = class {
1209
1372
  col: 0,
1210
1373
  row: 0
1211
1374
  })]: {
1212
- value: "",
1213
1375
  accessoryHtml: tableButtons.addColumn.render({
1214
1376
  disabled: (this.model.table.view?.target?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT
1215
1377
  }),
1216
- readonly: true
1378
+ className: "!bg-gridHeader",
1379
+ readonly: true,
1380
+ value: ""
1217
1381
  }
1218
1382
  };
1219
1383
  }
@@ -1221,7 +1385,7 @@ var TablePresentation = class {
1221
1385
 
1222
1386
  // packages/ui/react-ui-table/src/components/TableCellEditor/TableCellEditor.tsx
1223
1387
  import React6, { useCallback as useCallback3, useMemo as useMemo3, useRef } from "react";
1224
- import { FormatEnum as FormatEnum4 } from "@dxos/echo-schema";
1388
+ import { FormatEnum as FormatEnum5 } from "@dxos/echo-schema";
1225
1389
  import { invariant as invariant2 } from "@dxos/invariant";
1226
1390
  import { cellQuery, editorKeys, parseCellIndex, useGridContext as useGridContext2, GridCellEditor } from "@dxos/react-ui-grid";
1227
1391
 
@@ -1265,7 +1429,7 @@ var completion = ({ debug, onQuery, onMatch }) => {
1265
1429
  };
1266
1430
 
1267
1431
  // packages/ui/react-ui-table/src/components/TableCellEditor/TableCellEditor.tsx
1268
- var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/components/TableCellEditor/TableCellEditor.tsx";
1432
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/components/TableCellEditor/TableCellEditor.tsx";
1269
1433
  var newValue = Symbol.for("newValue");
1270
1434
  var createOption = (text) => ({
1271
1435
  [newValue]: true,
@@ -1283,7 +1447,7 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
1283
1447
  const field = model.projection.view.fields[col];
1284
1448
  const fieldProjection2 = model.projection.getFieldProjection(field.id);
1285
1449
  invariant2(fieldProjection2, void 0, {
1286
- F: __dxlog_file4,
1450
+ F: __dxlog_file3,
1287
1451
  L: 67,
1288
1452
  S: void 0,
1289
1453
  A: [
@@ -1357,7 +1521,7 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
1357
1521
  ];
1358
1522
  if (onQuery) {
1359
1523
  switch (fieldProjection.props.format) {
1360
- case FormatEnum4.Ref: {
1524
+ case FormatEnum5.Ref: {
1361
1525
  extension2.push([
1362
1526
  completion({
1363
1527
  onQuery: (text) => onQuery(fieldProjection, text),
@@ -1433,7 +1597,7 @@ var determineNavigationDelta = ({ key, shift }) => {
1433
1597
  };
1434
1598
 
1435
1599
  // packages/ui/react-ui-table/src/components/Table/Table.tsx
1436
- var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/components/Table/Table.tsx";
1600
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/components/Table/Table.tsx";
1437
1601
  var inlineEndLine = "[&>.dx-grid]:relative [&>.dx-grid]:after:absolute [&>.dx-grid]:after:inset-block-0 [&>.dx-grid]:after:-inline-end-px [&>.dx-grid]:after:is-px [&>.dx-grid]:after:bg-separator";
1438
1602
  var blockEndLine = "[&>.dx-grid]:before:absolute [&>.dx-grid]:before:inset-inline-0 [&>.dx-grid]:before:-block-end-px [&>.dx-grid]:before:bs-px [&>.dx-grid]:before:bg-separator";
1439
1603
  var frozen = {
@@ -1501,6 +1665,10 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
1501
1665
  modals.showReferencePanel(data.targetId, data.schemaId);
1502
1666
  break;
1503
1667
  }
1668
+ case "sort": {
1669
+ model?.sorting?.toggleSort(data.fieldId);
1670
+ break;
1671
+ }
1504
1672
  }
1505
1673
  return;
1506
1674
  }
@@ -1601,8 +1769,8 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
1601
1769
  if (model && props.referenceSchema && field.referencePath) {
1602
1770
  const space = getSpace4(model.table);
1603
1771
  invariant3(space, void 0, {
1604
- F: __dxlog_file5,
1605
- L: 260,
1772
+ F: __dxlog_file4,
1773
+ L: 264,
1606
1774
  S: void 0,
1607
1775
  A: [
1608
1776
  "space",
@@ -1613,7 +1781,7 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
1613
1781
  if (schema) {
1614
1782
  const { objects } = await space.db.query(Filter.schema(schema)).run();
1615
1783
  const options = objects.map((obj) => {
1616
- const value = getValue3(obj, field.referencePath);
1784
+ const value = getValue4(obj, field.referencePath);
1617
1785
  if (!value || typeof value !== "string") {
1618
1786
  return void 0;
1619
1787
  }
@@ -1686,79 +1854,76 @@ var Table = {
1686
1854
  Main: TableMain
1687
1855
  };
1688
1856
 
1689
- // packages/ui/react-ui-table/src/components/Toolbar/Toolbar.tsx
1690
- import { createContext } from "@radix-ui/react-context";
1691
- import React9 from "react";
1692
- import { Toolbar as NaturalToolbar2, useTranslation as useTranslation4 } from "@dxos/react-ui";
1693
-
1694
- // packages/ui/react-ui-table/src/components/Toolbar/common.tsx
1695
- import React8 from "react";
1696
- import { Icon, Toolbar as NaturalToolbar, Tooltip } from "@dxos/react-ui";
1697
- var buttonStyles = "min-bs-0 p-2";
1698
- var tooltipProps = {
1699
- side: "top",
1700
- classNames: "z-10"
1701
- };
1702
- var ToolbarSeparator = () => /* @__PURE__ */ React8.createElement("div", {
1703
- role: "separator",
1704
- className: "grow"
1705
- });
1706
- var ToolbarButton = ({ icon, children, ...props }) => {
1707
- return /* @__PURE__ */ React8.createElement(Tooltip.Root, null, /* @__PURE__ */ React8.createElement(Tooltip.Trigger, {
1708
- asChild: true
1709
- }, /* @__PURE__ */ React8.createElement(NaturalToolbar.Button, {
1710
- variant: "ghost",
1711
- ...props,
1712
- classNames: buttonStyles
1713
- }, /* @__PURE__ */ React8.createElement(Icon, {
1714
- icon,
1715
- size: 5
1716
- }), /* @__PURE__ */ React8.createElement("span", {
1717
- className: "sr-only"
1718
- }, children))), /* @__PURE__ */ React8.createElement(Tooltip.Portal, null, /* @__PURE__ */ React8.createElement(Tooltip.Content, tooltipProps, children, /* @__PURE__ */ React8.createElement(Tooltip.Arrow, null))));
1719
- };
1720
-
1721
- // packages/ui/react-ui-table/src/components/Toolbar/Toolbar.tsx
1722
- var [ToolbarContextProvider, useToolbarContext] = createContext("Toolbar");
1723
- var ToolbarRoot = ({ classNames, children, onAction }) => {
1724
- return /* @__PURE__ */ React9.createElement(ToolbarContextProvider, {
1725
- onAction
1726
- }, /* @__PURE__ */ React9.createElement(NaturalToolbar2.Root, {
1727
- classNames: [
1728
- "is-full shrink-0 overflow-x-auto overflow-y-hidden p-1 attention-surface",
1729
- classNames
1730
- ]
1731
- }, children));
1732
- };
1733
- var Editing = () => {
1734
- const { onAction } = useToolbarContext("Editing");
1735
- const { t } = useTranslation4(translationKey);
1736
- return /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(ToolbarButton, {
1737
- value: "add-row",
1857
+ // packages/ui/react-ui-table/src/components/TableToolbar/TableToolbar.tsx
1858
+ import React8, { useCallback as useCallback5, useMemo as useMemo5 } from "react";
1859
+ import { create as create3 } from "@dxos/live-object";
1860
+ import { createGapSeparator, createMenuAction, MenuProvider, ToolbarMenu, useMenuActions } from "@dxos/react-ui-menu";
1861
+ var createTableToolbarActions = (state) => {
1862
+ const add = createMenuAction("add-row", {
1863
+ type: "add-row",
1738
1864
  icon: "ph--plus--regular",
1739
- "data-testid": "table.toolbar.add-row",
1740
- onClick: () => onAction?.({
1741
- type: "add-row"
1742
- })
1743
- }, t("add row")));
1744
- };
1745
- var Actions = () => {
1746
- const { onAction } = useToolbarContext("Actions");
1747
- const { t } = useTranslation4(translationKey);
1748
- return /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(ToolbarButton, {
1749
- value: "comment",
1865
+ label: [
1866
+ "add row",
1867
+ {
1868
+ ns: translationKey
1869
+ }
1870
+ ],
1871
+ testId: "table.toolbar.add-row"
1872
+ });
1873
+ const save = createMenuAction("save-view", {
1874
+ type: "save-view",
1875
+ icon: "ph--floppy-disk--regular",
1876
+ label: [
1877
+ "save view label",
1878
+ {
1879
+ ns: translationKey
1880
+ }
1881
+ ],
1882
+ testId: "table.toolbar.save-view",
1883
+ iconOnly: false,
1884
+ hidden: !state.viewDirty
1885
+ });
1886
+ const gap = createGapSeparator();
1887
+ const comment = createMenuAction("comment", {
1888
+ type: "comment",
1750
1889
  icon: "ph--chat-text--regular",
1751
- "data-testid": "table.toolbar.comment",
1752
- onClick: () => onAction?.({
1753
- type: "comment"
1754
- })
1755
- }, t("create comment")));
1890
+ label: [
1891
+ "create comment",
1892
+ {
1893
+ ns: translationKey
1894
+ }
1895
+ ],
1896
+ testId: "table.toolbar.comment"
1897
+ });
1898
+ const nodes = [
1899
+ add,
1900
+ save,
1901
+ ...gap.nodes,
1902
+ comment
1903
+ ];
1904
+ return {
1905
+ nodes,
1906
+ edges: nodes.map(({ id: target }) => ({
1907
+ source: "root",
1908
+ target
1909
+ }))
1910
+ };
1756
1911
  };
1757
- var Toolbar = {
1758
- Root: ToolbarRoot,
1759
- Editing,
1760
- Actions,
1761
- Separator: ToolbarSeparator
1912
+ var TableToolbar = ({ classNames, viewDirty, attendableId, onAction }) => {
1913
+ const state = useMemo5(() => create3({
1914
+ viewDirty
1915
+ }), []);
1916
+ const actionsCreator = useCallback5(() => createTableToolbarActions(state), [
1917
+ state
1918
+ ]);
1919
+ const menu = useMenuActions(actionsCreator);
1920
+ return /* @__PURE__ */ React8.createElement(MenuProvider, {
1921
+ ...menu,
1922
+ attendableId,
1923
+ onAction
1924
+ }, /* @__PURE__ */ React8.createElement(ToolbarMenu, {
1925
+ classNames
1926
+ }));
1762
1927
  };
1763
1928
 
1764
1929
  // packages/ui/react-ui-table/src/hooks/useTableModel.ts
@@ -1805,8 +1970,8 @@ export {
1805
1970
  TableModel,
1806
1971
  TablePresentation,
1807
1972
  TableSchema,
1973
+ TableToolbar,
1808
1974
  TableType,
1809
- Toolbar,
1810
1975
  initializeTable,
1811
1976
  renderCheckbox,
1812
1977
  renderSwitch,
@@ -1814,7 +1979,6 @@ export {
1814
1979
  tableControls,
1815
1980
  touch,
1816
1981
  translations_default as translations,
1817
- useTableModel,
1818
- useToolbarContext
1982
+ useTableModel
1819
1983
  };
1820
1984
  //# sourceMappingURL=index.mjs.map