@dxos/react-ui-table 0.8.3-staging.0fa589b → 0.8.4-main.f9ba587

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 (68) hide show
  1. package/dist/lib/browser/{chunk-TL7CKQQV.mjs → chunk-Q5QQXJUO.mjs} +2 -2
  2. package/dist/lib/browser/index.mjs +552 -227
  3. package/dist/lib/browser/index.mjs.map +4 -4
  4. package/dist/lib/browser/meta.json +1 -1
  5. package/dist/lib/browser/types/index.mjs +1 -1
  6. package/dist/lib/node-esm/{chunk-GBT6GD4G.mjs → chunk-ZNOU6LHG.mjs} +2 -2
  7. package/dist/lib/node-esm/index.mjs +552 -227
  8. package/dist/lib/node-esm/index.mjs.map +4 -4
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/lib/node-esm/types/index.mjs +1 -1
  11. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +1 -1
  12. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  13. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  14. package/dist/types/src/components/Table/Table.stories.d.ts +1 -1
  15. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  16. package/dist/types/src/components/Table/relations.stories.d.ts +1 -1
  17. package/dist/types/src/components/Table/relations.stories.d.ts.map +1 -1
  18. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +2 -1
  19. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
  20. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  21. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +1 -1
  22. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  23. package/dist/types/src/components/testing.d.ts +2 -2
  24. package/dist/types/src/hooks/index.d.ts +1 -0
  25. package/dist/types/src/hooks/index.d.ts.map +1 -1
  26. package/dist/types/src/hooks/useAddRow.d.ts +13 -0
  27. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -0
  28. package/dist/types/src/index.d.ts +1 -1
  29. package/dist/types/src/index.d.ts.map +1 -1
  30. package/dist/types/src/model/table-model.d.ts +42 -6
  31. package/dist/types/src/model/table-model.d.ts.map +1 -1
  32. package/dist/types/src/model/table-presentation.d.ts +8 -0
  33. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  34. package/dist/types/src/playwright/playwright.config.d.ts +3 -0
  35. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -0
  36. package/dist/types/src/translations.d.ts +3 -4
  37. package/dist/types/src/translations.d.ts.map +1 -1
  38. package/dist/types/src/types/table.d.ts +4 -4
  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/tsconfig.tsbuildinfo +1 -1
  42. package/package.json +38 -40
  43. package/src/components/Table/DynamicTable.stories.tsx +2 -2
  44. package/src/components/Table/Table.stories.tsx +18 -12
  45. package/src/components/Table/Table.tsx +17 -4
  46. package/src/components/Table/relations.stories.tsx +4 -5
  47. package/src/components/TableCellEditor/CellValidationMessage.tsx +6 -1
  48. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +3 -3
  49. package/src/components/TableCellEditor/TableCellEditor.tsx +54 -15
  50. package/src/hooks/index.ts +1 -0
  51. package/src/hooks/useAddRow.ts +36 -0
  52. package/src/index.ts +1 -1
  53. package/src/model/table-model.ts +220 -34
  54. package/src/model/table-presentation.ts +213 -130
  55. package/src/playwright/playwright.config.ts +17 -0
  56. package/src/playwright/smoke.spec.ts +9 -7
  57. package/src/translations.ts +5 -3
  58. package/src/util/table-buttons.ts +24 -1
  59. package/dist/lib/node/chunk-O2VPBNS5.cjs +0 -50
  60. package/dist/lib/node/chunk-O2VPBNS5.cjs.map +0 -7
  61. package/dist/lib/node/index.cjs +0 -2739
  62. package/dist/lib/node/index.cjs.map +0 -7
  63. package/dist/lib/node/meta.json +0 -1
  64. package/dist/lib/node/types/index.cjs +0 -31
  65. package/dist/lib/node/types/index.cjs.map +0 -7
  66. package/src/playwright/playwright.config.cts +0 -18
  67. /package/dist/lib/browser/{chunk-TL7CKQQV.mjs.map → chunk-Q5QQXJUO.mjs.map} +0 -0
  68. /package/dist/lib/node-esm/{chunk-GBT6GD4G.mjs.map → chunk-ZNOU6LHG.mjs.map} +0 -0
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  TableSchema,
3
3
  TableType
4
- } from "./chunk-TL7CKQQV.mjs";
4
+ } from "./chunk-Q5QQXJUO.mjs";
5
5
 
6
- // packages/ui/react-ui-table/src/translations.ts
7
- var translationKey = "table";
8
- var translations_default = [
6
+ // src/translations.ts
7
+ var translationKey = "react-ui-table";
8
+ var translations = [
9
9
  {
10
10
  "en-US": {
11
11
  [translationKey]: {
@@ -35,9 +35,9 @@ var translations_default = [
35
35
  }
36
36
  ];
37
37
 
38
- // packages/ui/react-ui-table/src/components/Table/Table.tsx
38
+ // src/components/Table/Table.tsx
39
39
  import { useSignals as _useSignals8 } from "@preact-signals/safe-react/tracking";
40
- import React8, { forwardRef, useCallback as useCallback5, useImperativeHandle, useState as useState3, useMemo as useMemo5, useEffect as useEffect3 } from "react";
40
+ import React8, { forwardRef, useCallback as useCallback5, useImperativeHandle, useState as useState3, useMemo as useMemo5, useEffect as useEffect4 } from "react";
41
41
  import { getValue as getValue4 } from "@dxos/echo-schema";
42
42
  import { invariant as invariant4 } from "@dxos/invariant";
43
43
  import { Filter } from "@dxos/react-client/echo";
@@ -47,7 +47,7 @@ import { closestCell, Grid, gridSeparatorInlineEnd, gridSeparatorBlockEnd } from
47
47
  import { mx as mx2 } from "@dxos/react-ui-theme";
48
48
  import { isNotFalsy, safeParseInt } from "@dxos/util";
49
49
 
50
- // packages/ui/react-ui-table/src/components/Table/ColumnActionsMenu.tsx
50
+ // src/components/Table/ColumnActionsMenu.tsx
51
51
  import { useSignals as _useSignals } from "@preact-signals/safe-react/tracking";
52
52
  import React from "react";
53
53
  import { DropdownMenu, useTranslation } from "@dxos/react-ui";
@@ -88,7 +88,7 @@ var ColumnActionsMenu = ({ model, modals }) => {
88
88
  }
89
89
  };
90
90
 
91
- // packages/ui/react-ui-table/src/components/Table/ColumnSettings.tsx
91
+ // src/components/Table/ColumnSettings.tsx
92
92
  import { useSignals as _useSignals2 } from "@preact-signals/safe-react/tracking";
93
93
  import React2, { useCallback, useEffect, useMemo, useState } from "react";
94
94
  import { Popover } from "@dxos/react-ui";
@@ -162,7 +162,7 @@ var ColumnSettings = ({ model, modals, onNewColumn }) => {
162
162
  }
163
163
  };
164
164
 
165
- // packages/ui/react-ui-table/src/components/Table/CreateRefPanel.tsx
165
+ // src/components/Table/CreateRefPanel.tsx
166
166
  import { useSignals as _useSignals3 } from "@preact-signals/safe-react/tracking";
167
167
  import { Schema } from "effect";
168
168
  import React3, { useCallback as useCallback2, useMemo as useMemo2 } from "react";
@@ -242,7 +242,7 @@ var CreateRefPanel = ({ model, modals, __gridScope }) => {
242
242
  }
243
243
  };
244
244
 
245
- // packages/ui/react-ui-table/src/components/Table/RowActionsMenu.tsx
245
+ // src/components/Table/RowActionsMenu.tsx
246
246
  import { useSignals as _useSignals4 } from "@preact-signals/safe-react/tracking";
247
247
  import React4 from "react";
248
248
  import { DropdownMenu as DropdownMenu2, useTranslation as useTranslation2 } from "@dxos/react-ui";
@@ -277,20 +277,22 @@ var RowActionsMenu = ({ model, modals }) => {
277
277
  }
278
278
  };
279
279
 
280
- // packages/ui/react-ui-table/src/model/table-model.ts
280
+ // src/model/table-model.ts
281
281
  import { computed as computed3, effect as effect2, signal as signal3 } from "@preact/signals-core";
282
282
  import { Resource as Resource2 } from "@dxos/context";
283
- import { FormatEnum as FormatEnum3, getValue as getValue2, setValue, getSnapshot, getSchema } from "@dxos/echo-schema";
283
+ import { Ref as Ref3 } from "@dxos/echo";
284
+ import { FormatEnum as FormatEnum3, getValue as getValue2, setValue, getSnapshot, getSchema, toEffectSchema } from "@dxos/echo-schema";
284
285
  import { invariant } from "@dxos/invariant";
285
- import { isLiveObject, makeRef } from "@dxos/live-object";
286
+ import { ObjectId } from "@dxos/keys";
287
+ import { isLiveObject } from "@dxos/live-object";
286
288
  import { formatForEditing, parseValue } from "@dxos/react-ui-form";
287
289
  import { validateSchema } from "@dxos/schema";
288
290
 
289
- // packages/ui/react-ui-table/src/model/selection-model.ts
291
+ // src/model/selection-model.ts
290
292
  import { computed, effect, signal } from "@preact/signals-core";
291
293
  import { Resource } from "@dxos/context";
292
294
 
293
- // packages/ui/react-ui-table/src/util/dynamic-table.ts
295
+ // src/util/dynamic-table.ts
294
296
  import { Obj, Ref } from "@dxos/echo";
295
297
  import { getTypename, toJsonSchema } from "@dxos/echo-schema";
296
298
  import { createView, getSchemaFromPropertyDefinitions, ViewProjection } from "@dxos/schema";
@@ -367,10 +369,10 @@ var setProperties = (view, projection, properties) => {
367
369
  }
368
370
  };
369
371
 
370
- // packages/ui/react-ui-table/src/util/signals.ts
372
+ // src/util/signals.ts
371
373
  var touch = (value) => value;
372
374
 
373
- // packages/ui/react-ui-table/src/util/table.ts
375
+ // src/util/table.ts
374
376
  import { Schema as Schema2 } from "effect";
375
377
  import { Format, FormatEnum, Ref as Ref2, TypedObject, TypeEnum } from "@dxos/echo-schema";
376
378
  import { PublicKey } from "@dxos/react-client";
@@ -463,12 +465,13 @@ var createProjection = (schema, view) => {
463
465
  return projection;
464
466
  };
465
467
 
466
- // packages/ui/react-ui-table/src/util/table-buttons.ts
468
+ // src/util/table-buttons.ts
467
469
  var BUTTON_IDENTIFIERS = {
468
470
  columnSettings: "data-table-column-settings-button",
469
471
  newColumn: "data-table-new-column-button",
470
472
  referencedCell: "data-table-ref-cell-button",
471
473
  rowMenu: "data-table-row-menu-button",
474
+ saveDraftRow: "data-table-save-draft-row-button",
472
475
  sort: "data-table-sort-button"
473
476
  };
474
477
  var createButton = ({ attr, data, disabled = false, icon, testId, type = "primary" }) => {
@@ -551,14 +554,34 @@ var sortButton = {
551
554
  direction: el.getAttribute("data-direction")
552
555
  })
553
556
  };
557
+ var saveDraftRowButton = {
558
+ attr: BUTTON_IDENTIFIERS.saveDraftRow,
559
+ icon: "ph--floppy-disk--regular",
560
+ render: ({ rowIndex, disabled }) => {
561
+ return createButton({
562
+ attr: BUTTON_IDENTIFIERS.saveDraftRow,
563
+ icon: saveDraftRowButton.icon,
564
+ disabled,
565
+ data: {
566
+ "data-row-index": rowIndex.toString()
567
+ },
568
+ testId: "table-save-draft-row-button"
569
+ });
570
+ },
571
+ getData: (el) => ({
572
+ type: "saveDraftRow",
573
+ rowIndex: Number(el.getAttribute("data-row-index"))
574
+ })
575
+ };
554
576
  var tableButtons = {
555
577
  addColumn: addColumnButton,
556
578
  columnSettings: columnSettingsButton,
557
579
  rowMenu: rowMenuButton,
580
+ saveDraftRow: saveDraftRowButton,
558
581
  sort: sortButton
559
582
  };
560
583
 
561
- // packages/ui/react-ui-table/src/util/table-controls.ts
584
+ // src/util/table-controls.ts
562
585
  var CONTROL_IDENTIFIERS = {
563
586
  checkbox: "data-table-checkbox",
564
587
  switch: "data-table-switch"
@@ -612,7 +635,7 @@ var tableControls = {
612
635
  }
613
636
  };
614
637
 
615
- // packages/ui/react-ui-table/src/util/schema.ts
638
+ // src/util/schema.ts
616
639
  import { Schema as Schema3 } from "effect";
617
640
  import { isTypeLiteral, TypeLiteral } from "effect/SchemaAST";
618
641
  var narrowSchema = (schema, paths) => {
@@ -627,7 +650,7 @@ var narrowSchema = (schema, paths) => {
627
650
  return void 0;
628
651
  };
629
652
 
630
- // packages/ui/react-ui-table/src/model/selection-model.ts
653
+ // src/model/selection-model.ts
631
654
  var SelectionModel = class extends Resource {
632
655
  constructor(_rows, _selectionMode, _onSelectionChanged) {
633
656
  super(), this._rows = _rows, this._selectionMode = _selectionMode, this._onSelectionChanged = _onSelectionChanged, this._selection = signal(/* @__PURE__ */ new Set()), this._validSelectedIds = computed(() => {
@@ -702,7 +725,7 @@ var SelectionModel = class extends Resource {
702
725
  }
703
726
  };
704
727
 
705
- // packages/ui/react-ui-table/src/model/table-sorting.ts
728
+ // src/model/table-sorting.ts
706
729
  import { computed as computed2, signal as signal2 } from "@preact/signals-core";
707
730
  import orderBy from "lodash.orderby";
708
731
  import { getValue, FormatEnum as FormatEnum2, TypeEnum as TypeEnum2 } from "@dxos/echo-schema";
@@ -867,7 +890,7 @@ var TableSorting = class {
867
890
  }
868
891
  };
869
892
 
870
- // packages/ui/react-ui-table/src/util/tag.ts
893
+ // src/util/tag.ts
871
894
  var TAG_PATTERN_REGEX = /\[([^\]]+)\]\(([^)]+)\)/g;
872
895
  var extractTagIds = (value) => {
873
896
  if (typeof value !== "string") {
@@ -883,8 +906,8 @@ var extractTagIds = (value) => {
883
906
  return ids.length > 0 ? ids : void 0;
884
907
  };
885
908
 
886
- // packages/ui/react-ui-table/src/model/table-model.ts
887
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/model/table-model.ts";
909
+ // src/model/table-model.ts
910
+ var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-table/src/model/table-model.ts";
888
911
  var defaultFeatures = {
889
912
  selection: {
890
913
  enabled: true,
@@ -910,6 +933,7 @@ var TableModel = class extends Resource2 {
910
933
  }
911
934
  });
912
935
  this._rows = signal3([]);
936
+ this._draftRows = signal3([]);
913
937
  //
914
938
  // Data
915
939
  //
@@ -917,10 +941,31 @@ var TableModel = class extends Resource2 {
917
941
  this._rows.value = obj;
918
942
  };
919
943
  this.getRowCount = () => this._rows.value.length;
944
+ /**
945
+ * @reactive
946
+ */
947
+ this.getDraftRowCount = () => this._draftRows.value.length;
920
948
  this.getColumnCount = () => this._view?.fields.length ?? 0;
921
- this.insertRow = (rowIndex) => {
922
- const row = rowIndex !== void 0 ? this._sorting.getDataIndex(rowIndex) : this._rows.value.length;
923
- this._onInsertRow?.(row);
949
+ this.insertRow = () => {
950
+ const result = this._onInsertRow?.();
951
+ if (result === false && this._draftRows.value.length === 0) {
952
+ this.createDraftRow();
953
+ }
954
+ };
955
+ this.commitDraftRow = (draftRowIndex) => {
956
+ if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.value.length) {
957
+ return false;
958
+ }
959
+ const draftRow = this._draftRows.value[draftRowIndex];
960
+ if (!draftRow.valid) {
961
+ return false;
962
+ }
963
+ const success = this._onInsertRow?.(draftRow.data);
964
+ if (success) {
965
+ const newDraftRows = this._draftRows.value.filter((_, index) => index !== draftRowIndex);
966
+ this._draftRows.value = newDraftRows;
967
+ }
968
+ return success ?? false;
924
969
  };
925
970
  this.deleteRow = (rowIndex) => {
926
971
  const row = this._sorting.getDataIndex(rowIndex);
@@ -944,14 +989,24 @@ var TableModel = class extends Resource2 {
944
989
  this._onRowAction(actionId, data);
945
990
  }
946
991
  };
947
- this.getCellData = ({ col, row }) => {
992
+ this.getCellData = (cell) => {
993
+ const { col, row, plane } = cell;
948
994
  const fields = this._view?.fields ?? [];
949
995
  if (col < 0 || col >= fields.length) {
950
996
  return void 0;
951
997
  }
952
998
  const field = fields[col];
953
- const dataIndex = this._sorting.getDataIndex(row);
954
- const value = getValue2(this._rows.value[dataIndex], field.path);
999
+ let value;
1000
+ if (plane === "frozenRowsEnd") {
1001
+ if (row >= 0 && row < this._draftRows.value.length) {
1002
+ value = getValue2(this._draftRows.value[row].data, field.path);
1003
+ } else {
1004
+ return void 0;
1005
+ }
1006
+ } else {
1007
+ const dataIndex = this._sorting.getDataIndex(row);
1008
+ value = getValue2(this._rows.value[dataIndex], field.path);
1009
+ }
955
1010
  if (value == null) {
956
1011
  return "";
957
1012
  }
@@ -972,8 +1027,7 @@ var TableModel = class extends Resource2 {
972
1027
  }
973
1028
  }
974
1029
  };
975
- this.validateCellData = async ({ col, row }, value) => {
976
- const rowIdx = this._sorting.getDataIndex(row);
1030
+ this.validateCellData = async ({ col, row, plane }, value) => {
977
1031
  const fields = this._view?.fields ?? [];
978
1032
  if (col < 0 || col >= fields.length) {
979
1033
  return {
@@ -983,52 +1037,82 @@ var TableModel = class extends Resource2 {
983
1037
  }
984
1038
  const field = fields[col];
985
1039
  const { props } = this._projection.getFieldProjection(field.id);
986
- const currentRow = this._rows.value[rowIdx];
987
- invariant(currentRow, "Invalid row index", {
988
- F: __dxlog_file,
989
- L: 355,
990
- S: this,
991
- A: [
992
- "currentRow",
993
- "'Invalid row index'"
994
- ]
995
- });
996
- const snapshot = getSnapshot(currentRow);
997
1040
  const transformedValue = editorTextToCellValue(props, value);
998
- setValue(snapshot, field.path, transformedValue);
999
- const schema = getSchema(currentRow);
1000
- invariant(schema, void 0, {
1001
- F: __dxlog_file,
1002
- L: 365,
1003
- S: this,
1004
- A: [
1005
- "schema",
1006
- ""
1007
- ]
1008
- });
1009
- const validationResult = validateSchema(schema, snapshot);
1010
- if (validationResult && validationResult.length > 0) {
1011
- const error = validationResult[0];
1012
- invariant(error.path === field.path, void 0, {
1041
+ const isDraftRow = plane === "frozenRowsEnd";
1042
+ if (isDraftRow) {
1043
+ const isInvalidDraftRowIndex = row < 0 || row >= this._draftRows.value.length;
1044
+ if (isInvalidDraftRowIndex) {
1045
+ return {
1046
+ valid: false,
1047
+ error: "Invalid draft row index"
1048
+ };
1049
+ }
1050
+ const draftRow = this._draftRows.value[row];
1051
+ const snapshot = {
1052
+ ...draftRow.data
1053
+ };
1054
+ setValue(snapshot, field.path, transformedValue);
1055
+ const validationErrors = this.validateDraftRowData(snapshot);
1056
+ if (validationErrors.length > 0) {
1057
+ const error = validationErrors.find((err) => err.path === field.path);
1058
+ if (error) {
1059
+ return {
1060
+ valid: false,
1061
+ error: error.message
1062
+ };
1063
+ }
1064
+ }
1065
+ return {
1066
+ valid: true
1067
+ };
1068
+ } else {
1069
+ const rowIdx = this._sorting.getDataIndex(row);
1070
+ const currentRow = this._rows.value[rowIdx];
1071
+ invariant(currentRow, "Invalid row index", {
1013
1072
  F: __dxlog_file,
1014
- L: 370,
1073
+ L: 507,
1015
1074
  S: this,
1016
1075
  A: [
1017
- "error.path === field.path",
1076
+ "currentRow",
1077
+ "'Invalid row index'"
1078
+ ]
1079
+ });
1080
+ const snapshot = getSnapshot(currentRow);
1081
+ setValue(snapshot, field.path, transformedValue);
1082
+ const schema = getSchema(currentRow);
1083
+ invariant(schema, void 0, {
1084
+ F: __dxlog_file,
1085
+ L: 513,
1086
+ S: this,
1087
+ A: [
1088
+ "schema",
1018
1089
  ""
1019
1090
  ]
1020
1091
  });
1092
+ const validationResult = validateSchema(schema, snapshot);
1093
+ if (validationResult && validationResult.length > 0) {
1094
+ const error = validationResult[0];
1095
+ invariant(error.path === field.path, void 0, {
1096
+ F: __dxlog_file,
1097
+ L: 518,
1098
+ S: this,
1099
+ A: [
1100
+ "error.path === field.path",
1101
+ ""
1102
+ ]
1103
+ });
1104
+ return {
1105
+ valid: false,
1106
+ error: error.message
1107
+ };
1108
+ }
1021
1109
  return {
1022
- valid: false,
1023
- error: error.message
1110
+ valid: true
1024
1111
  };
1025
1112
  }
1026
- return {
1027
- valid: true
1028
- };
1029
1113
  };
1030
- this.setCellData = ({ col, row }, value) => {
1031
- const rowIdx = this._sorting.getDataIndex(row);
1114
+ this.setCellData = (cell, value) => {
1115
+ const { col, row, plane } = cell;
1032
1116
  const fields = this._view?.fields ?? [];
1033
1117
  if (col < 0 || col >= fields.length) {
1034
1118
  return;
@@ -1039,7 +1123,15 @@ var TableModel = class extends Resource2 {
1039
1123
  if (props.format === FormatEnum3.Ref && !isLiveObject(value)) {
1040
1124
  return;
1041
1125
  }
1042
- setValue(this._rows.value[rowIdx], field.path, transformedValue);
1126
+ if (plane === "frozenRowsEnd") {
1127
+ if (row >= 0 && row < this._draftRows.value.length) {
1128
+ setValue(this._draftRows.value[row].data, field.path, transformedValue);
1129
+ this.validateDraftRow(row);
1130
+ }
1131
+ } else {
1132
+ const rowIdx = this._sorting.getDataIndex(row);
1133
+ setValue(this._rows.value[rowIdx], field.path, transformedValue);
1134
+ }
1043
1135
  };
1044
1136
  this._id = id;
1045
1137
  this._space = space;
@@ -1051,7 +1143,7 @@ var TableModel = class extends Resource2 {
1051
1143
  };
1052
1144
  invariant(!(this._features.dataEditable && this._features.selection.enabled && this._features.selection.mode === "single"), "Single selection is not compatible with editable tables.", {
1053
1145
  F: __dxlog_file,
1054
- L: 126,
1146
+ L: 145,
1055
1147
  S: this,
1056
1148
  A: [
1057
1149
  "!(this._features.dataEditable && this._features.selection.enabled && this._features.selection.mode === 'single')",
@@ -1111,7 +1203,7 @@ var TableModel = class extends Resource2 {
1111
1203
  get columnMeta() {
1112
1204
  invariant(this._columnMeta, void 0, {
1113
1205
  F: __dxlog_file,
1114
- L: 195,
1206
+ L: 214,
1115
1207
  S: this,
1116
1208
  A: [
1117
1209
  "this._columnMeta",
@@ -1189,6 +1281,68 @@ var TableModel = class extends Resource2 {
1189
1281
  return () => rowEffects.forEach((cleanup) => cleanup());
1190
1282
  });
1191
1283
  this._ctx.onDispose(rowEffectManager);
1284
+ const draftRowsWatcher = effect2(() => {
1285
+ touch(this._draftRows.value);
1286
+ this._onRowOrderChange?.();
1287
+ });
1288
+ this._ctx.onDispose(draftRowsWatcher);
1289
+ }
1290
+ // TODO(ZaymonFC): Return .value instead of exposing the signal.
1291
+ get draftRows() {
1292
+ return this._draftRows;
1293
+ }
1294
+ /**
1295
+ * Checks if a specific field path has validation errors for a given draft row.
1296
+ * @param draftRowIndex - The index of the draft row to check
1297
+ * @param fieldPath - The path of the field to check for validation errors
1298
+ * @returns true if the field has validation errors, false otherwise
1299
+ */
1300
+ hasDraftRowValidationError(draftRowIndex, fieldPath) {
1301
+ if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.value.length) {
1302
+ return false;
1303
+ }
1304
+ const draftRow = this._draftRows.value[draftRowIndex];
1305
+ const validationErrors = draftRow.validationErrors || [];
1306
+ return validationErrors.some((error) => error.path === fieldPath);
1307
+ }
1308
+ createDraftRow() {
1309
+ if (!this._view) {
1310
+ return;
1311
+ }
1312
+ const draftData = {
1313
+ id: ObjectId.random()
1314
+ };
1315
+ const validationErrors = this.validateDraftRowData(draftData);
1316
+ const draftRow = {
1317
+ data: draftData,
1318
+ valid: validationErrors.length === 0,
1319
+ validationErrors
1320
+ };
1321
+ this._draftRows.value = [
1322
+ ...this._draftRows.value,
1323
+ draftRow
1324
+ ];
1325
+ }
1326
+ validateDraftRow(draftRowIndex) {
1327
+ if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.value.length) {
1328
+ return;
1329
+ }
1330
+ const draftRow = this._draftRows.value[draftRowIndex];
1331
+ const validationErrors = this.validateDraftRowData(draftRow.data);
1332
+ const updatedDraftRow = {
1333
+ ...draftRow,
1334
+ valid: validationErrors.length === 0,
1335
+ validationErrors
1336
+ };
1337
+ const newDraftRows = [
1338
+ ...this._draftRows.value
1339
+ ];
1340
+ newDraftRows[draftRowIndex] = updatedDraftRow;
1341
+ this._draftRows.value = newDraftRows;
1342
+ }
1343
+ validateDraftRowData(data) {
1344
+ const schema = toEffectSchema(this._projection.schema);
1345
+ return validateSchema(schema, data) || [];
1192
1346
  }
1193
1347
  /**
1194
1348
  * Updates a cell's value using a transform function.
@@ -1203,6 +1357,31 @@ var TableModel = class extends Resource2 {
1203
1357
  const updatedValue = update(value);
1204
1358
  setValue(this._rows.value[dataRow], field.path, updatedValue);
1205
1359
  }
1360
+ /**
1361
+ * Gets the domain-appropriate cell type for a given cell position.
1362
+ * This abstracts away the low-level grid plane concepts and provides domain-specific types.
1363
+ * @param cell - The cell position to classify
1364
+ * @returns The domain-appropriate cell type
1365
+ */
1366
+ getCellType(cell) {
1367
+ switch (cell.plane) {
1368
+ case "frozenRowsEnd":
1369
+ return "draft";
1370
+ case "frozenRowsStart":
1371
+ return "header";
1372
+ case "grid":
1373
+ default:
1374
+ return "standard";
1375
+ }
1376
+ }
1377
+ /**
1378
+ * Convenience method to check if a cell is a draft cell.
1379
+ * @param cell - The cell position to check
1380
+ * @returns true if the cell is a draft cell, false otherwise
1381
+ */
1382
+ isDraftCell(cell) {
1383
+ return this.getCellType(cell) === "draft";
1384
+ }
1206
1385
  //
1207
1386
  // Column Operations
1208
1387
  //
@@ -1250,7 +1429,7 @@ var editorTextToCellValue = (props, value) => {
1250
1429
  switch (props.format) {
1251
1430
  case FormatEnum3.Ref: {
1252
1431
  if (isLiveObject(value)) {
1253
- return makeRef(value);
1432
+ return Ref3.make(value);
1254
1433
  } else {
1255
1434
  return value;
1256
1435
  }
@@ -1277,10 +1456,10 @@ var editorTextToCellValue = (props, value) => {
1277
1456
  }
1278
1457
  };
1279
1458
 
1280
- // packages/ui/react-ui-table/src/model/modal-controller.ts
1459
+ // src/model/modal-controller.ts
1281
1460
  import { signal as signal4 } from "@preact/signals-core";
1282
1461
  import { log } from "@dxos/log";
1283
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/model/modal-controller.ts";
1462
+ var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/react-ui-table/src/model/modal-controller.ts";
1284
1463
  var ModalController = class {
1285
1464
  constructor() {
1286
1465
  this._state = signal4({
@@ -1365,10 +1544,11 @@ var ModalController = class {
1365
1544
  }
1366
1545
  };
1367
1546
 
1368
- // packages/ui/react-ui-table/src/model/table-presentation.ts
1547
+ // src/model/table-presentation.ts
1369
1548
  import { signal as signal5 } from "@preact/signals-core";
1370
1549
  import { isNotNullable } from "effect/Predicate";
1371
- import { FormatEnum as FormatEnum4, getObjectDXN, getValue as getValue3, TypeEnum as TypeEnum3 } from "@dxos/echo-schema";
1550
+ import { Obj as Obj2 } from "@dxos/echo";
1551
+ import { FormatEnum as FormatEnum4, getValue as getValue3, TypeEnum as TypeEnum3 } from "@dxos/echo-schema";
1372
1552
  import { cellClassesForFieldType, formatForDisplay as formatForDisplay2 } from "@dxos/react-ui-form";
1373
1553
  import { toPlaneCellIndex } from "@dxos/react-ui-grid";
1374
1554
  import { mx } from "@dxos/react-ui-theme";
@@ -1386,8 +1566,10 @@ var TablePresentation = class {
1386
1566
  })) {
1387
1567
  this.model = model;
1388
1568
  this._visibleRange = _visibleRange;
1569
+ this.fieldProjectionCache = /* @__PURE__ */ new Map();
1389
1570
  }
1390
1571
  getCells(range, plane) {
1572
+ this.fieldProjectionCache.clear();
1391
1573
  let cells;
1392
1574
  switch (plane) {
1393
1575
  case "grid":
@@ -1409,6 +1591,12 @@ var TablePresentation = class {
1409
1591
  case "fixedStartEnd":
1410
1592
  cells = this.getNewColumnCell();
1411
1593
  break;
1594
+ case "frozenRowsEnd":
1595
+ cells = this.getDraftRowCells(range);
1596
+ break;
1597
+ case "fixedEndEnd":
1598
+ cells = this.getDraftActionCells(range);
1599
+ break;
1412
1600
  default:
1413
1601
  cells = {};
1414
1602
  }
@@ -1419,139 +1607,178 @@ var TablePresentation = class {
1419
1607
  }
1420
1608
  return cells;
1421
1609
  }
1422
- getMainGridCells(range) {
1423
- const cells = {};
1424
- const fields = this.model.view?.fields ?? [];
1425
- const addCell = (obj, field, colIndex, displayIndex) => {
1426
- const { props } = this.model.projection.getFieldProjection(field.id);
1427
- const cell = {
1428
- get value() {
1429
- const value = getValue3(obj, field.path);
1430
- if (value == null) {
1431
- return "";
1432
- }
1433
- if (props.type === "array") {
1610
+ /**
1611
+ * Gets a field projection, using cache if available within the current render pass.
1612
+ */
1613
+ getFieldProjection(fieldId) {
1614
+ let projection = this.fieldProjectionCache.get(fieldId);
1615
+ if (!projection) {
1616
+ projection = this.model.projection.getFieldProjection(fieldId);
1617
+ this.fieldProjectionCache.set(fieldId, projection);
1618
+ }
1619
+ return projection;
1620
+ }
1621
+ createDataCell(cells, obj, field, colIndex, displayIndex) {
1622
+ const { props } = this.getFieldProjection(field.id);
1623
+ const cell = {
1624
+ get value() {
1625
+ const value = getValue3(obj, field.path);
1626
+ if (value == null) {
1627
+ return "";
1628
+ }
1629
+ if (props.type === "array") {
1630
+ return "";
1631
+ }
1632
+ switch (props.format) {
1633
+ case FormatEnum4.Boolean:
1634
+ case FormatEnum4.SingleSelect:
1635
+ case FormatEnum4.MultiSelect: {
1434
1636
  return "";
1435
1637
  }
1436
- switch (props.format) {
1437
- case FormatEnum4.Boolean:
1438
- case FormatEnum4.SingleSelect:
1439
- case FormatEnum4.MultiSelect: {
1638
+ case FormatEnum4.Ref: {
1639
+ if (!field.referencePath) {
1440
1640
  return "";
1441
1641
  }
1442
- case FormatEnum4.Ref: {
1443
- if (!field.referencePath) {
1444
- return "";
1445
- }
1446
- return getValue3(value.target, field.referencePath);
1447
- }
1448
- default: {
1449
- return formatForDisplay2({
1450
- type: props.type,
1451
- format: props.format,
1452
- value
1453
- });
1454
- }
1642
+ return getValue3(value.target, field.referencePath);
1643
+ }
1644
+ default: {
1645
+ return formatForDisplay2({
1646
+ type: props.type,
1647
+ format: props.format,
1648
+ value
1649
+ });
1455
1650
  }
1456
1651
  }
1457
- };
1458
- const classes = [];
1459
- const formatClasses = cellClassesForFieldType({
1460
- type: props.type,
1461
- format: props.format
1462
- });
1463
- if (formatClasses) {
1464
- classes.push(formatClasses);
1465
1652
  }
1466
- const rowSelectionClasses = cellClassesForRowSelection(this.model.selection.isObjectSelected(obj), this.model.selection.selectionMode);
1467
- if (rowSelectionClasses) {
1468
- classes.push(rowSelectionClasses);
1469
- }
1470
- if (classes.length > 0) {
1471
- cell.className = mx(classes.flat());
1653
+ };
1654
+ const classes = [];
1655
+ const formatClasses = cellClassesForFieldType({
1656
+ type: props.type,
1657
+ format: props.format
1658
+ });
1659
+ if (formatClasses) {
1660
+ classes.push(formatClasses);
1661
+ }
1662
+ const rowSelectionClasses = cellClassesForRowSelection(this.model.selection.isObjectSelected(obj), this.model.selection.selectionMode);
1663
+ if (rowSelectionClasses) {
1664
+ classes.push(rowSelectionClasses);
1665
+ }
1666
+ if (classes.length > 0) {
1667
+ cell.className = mx(classes.flat());
1668
+ }
1669
+ if (props.type === TypeEnum3.Array) {
1670
+ const targetArray = getValue3(obj, field.path);
1671
+ if (targetArray && Array.isArray(targetArray)) {
1672
+ const getLabel = (value) => {
1673
+ if (!value) {
1674
+ return void 0;
1675
+ }
1676
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1677
+ return String(value);
1678
+ }
1679
+ if (Array.isArray(value)) {
1680
+ return "Array";
1681
+ }
1682
+ if (typeof value === "object" && value.name) {
1683
+ return value.name;
1684
+ }
1685
+ if (typeof value === "object" && value.value) {
1686
+ return `${value.value}`;
1687
+ }
1688
+ return "Object";
1689
+ };
1690
+ const tags = targetArray.map(getLabel).filter(isNotNullable).map((title) => {
1691
+ return `<span class="dx-tag rounded-xl" data-hue="neutral">${title}</span>`;
1692
+ }).join("");
1693
+ cell.accessoryHtml = `<div role='none' class="flex flex-row gap-1 overflow-x-auto">${tags}</div>`;
1472
1694
  }
1473
- if (props.type === TypeEnum3.Array) {
1474
- const targetArray = getValue3(obj, field.path);
1475
- if (targetArray && Array.isArray(targetArray)) {
1476
- const getLabel = (value) => {
1477
- if (!value) {
1478
- return void 0;
1479
- }
1480
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1481
- return String(value);
1482
- }
1483
- if (Array.isArray(value)) {
1484
- return "Array";
1485
- }
1486
- if (typeof value === "object" && value.name) {
1487
- return value.name;
1488
- }
1489
- if (typeof value === "object" && value.value) {
1490
- return `${value.value}`;
1491
- }
1492
- return "Object";
1493
- };
1494
- const tags = targetArray.map(getLabel).filter(isNotNullable).map((title) => {
1495
- return `<span class="dx-tag rounded-xl" data-hue="neutral">${title}</span>`;
1496
- }).join("");
1497
- cell.accessoryHtml = `<div role='none' class="flex flex-row gap-1 overflow-x-auto">${tags}</div>`;
1498
- }
1695
+ }
1696
+ if (props.format === FormatEnum4.Ref && props.referenceSchema) {
1697
+ const targetObj = getValue3(obj, field.path)?.target;
1698
+ if (targetObj) {
1699
+ const dxn = Obj2.getDXN(targetObj)?.toString();
1700
+ cell.accessoryHtml = `<dx-ref-tag refId=${dxn} class="dx-button is-6 pli-[3px] pbe-[2px] min-bs-0 absolute inline-end-1"><dx-icon icon="ph--link-simple--regular"/></dx-ref-tag>`;
1499
1701
  }
1500
- if (props.format === FormatEnum4.Ref && props.referenceSchema) {
1501
- const targetObj = getValue3(obj, field.path)?.target;
1502
- if (targetObj) {
1503
- const dxn = getObjectDXN(targetObj)?.toString();
1504
- cell.accessoryHtml = `<dx-ref-tag refId=${dxn} class="dx-button is-6 pli-[3px] pbe-[2px] min-bs-0 absolute inline-end-1"><dx-icon icon="ph--link-simple--regular"/></dx-ref-tag>`;
1702
+ }
1703
+ if (props.format === FormatEnum4.Boolean) {
1704
+ const value = getValue3(obj, field.path);
1705
+ cell.accessoryHtml = tableControls.switch.render({
1706
+ colIndex,
1707
+ rowIndex: displayIndex,
1708
+ checked: value ?? false
1709
+ });
1710
+ cell.readonly = "no-text-select";
1711
+ }
1712
+ if (props.format === FormatEnum4.SingleSelect) {
1713
+ const value = getValue3(obj, field.path);
1714
+ const options = this.model.projection.getFieldProjection(field.id).props.options;
1715
+ if (options) {
1716
+ const option = options.find((o) => o.id === value);
1717
+ if (option) {
1718
+ cell.accessoryHtml = `<span class="dx-tag" data-hue="${option.color}">${option.title}</span>`;
1505
1719
  }
1506
1720
  }
1507
- if (props.format === FormatEnum4.Boolean) {
1508
- const value = getValue3(obj, field.path);
1509
- cell.accessoryHtml = tableControls.switch.render({
1510
- colIndex,
1511
- rowIndex: displayIndex,
1512
- checked: value ?? false
1513
- });
1514
- cell.readonly = "no-text-select";
1515
- }
1516
- if (props.format === FormatEnum4.SingleSelect) {
1517
- const value = getValue3(obj, field.path);
1518
- const options = this.model.projection.getFieldProjection(field.id).props.options;
1519
- if (options) {
1721
+ }
1722
+ if (props.format === FormatEnum4.MultiSelect) {
1723
+ const values = getValue3(obj, field.path);
1724
+ const options = this.model.projection.getFieldProjection(field.id).props.options;
1725
+ if (options && values && values.length > 0) {
1726
+ const tags = values.map((value) => {
1520
1727
  const option = options.find((o) => o.id === value);
1521
1728
  if (option) {
1522
- cell.accessoryHtml = `<span class="dx-tag" data-hue="${option.color}">${option.title}</span>`;
1729
+ return `<span class="dx-tag" data-hue="${option.color}">${option.title}</span>`;
1523
1730
  }
1731
+ return null;
1732
+ }).filter(Boolean).join("");
1733
+ if (tags) {
1734
+ cell.accessoryHtml = tags;
1524
1735
  }
1525
1736
  }
1526
- if (props.format === FormatEnum4.MultiSelect) {
1527
- const values = getValue3(obj, field.path);
1528
- const options = this.model.projection.getFieldProjection(field.id).props.options;
1529
- if (options && values && values.length > 0) {
1530
- const tags = values.map((value) => {
1531
- const option = options.find((o) => o.id === value);
1532
- if (option) {
1533
- return `<span class="dx-tag" data-hue="${option.color}">${option.title}</span>`;
1534
- }
1535
- return null;
1536
- }).filter(Boolean).join(" ");
1537
- if (tags) {
1538
- cell.accessoryHtml = `<div role='none' class="flex flex-row gap-1">${tags}</div>`;
1539
- }
1737
+ }
1738
+ const idx = toPlaneCellIndex({
1739
+ col: colIndex,
1740
+ row: displayIndex
1741
+ });
1742
+ cells[idx] = cell;
1743
+ }
1744
+ getMainGridCells(range) {
1745
+ const cells = {};
1746
+ const fields = this.model.view?.fields ?? [];
1747
+ for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
1748
+ for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1749
+ const field = fields[col];
1750
+ if (!field) {
1751
+ continue;
1540
1752
  }
1753
+ this.createDataCell(cells, this.model.rows.value[row], field, col, row);
1541
1754
  }
1542
- const idx = toPlaneCellIndex({
1543
- col: colIndex,
1544
- row: displayIndex
1545
- });
1546
- cells[idx] = cell;
1547
- };
1548
- for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
1755
+ }
1756
+ return cells;
1757
+ }
1758
+ getDraftRowCells(range) {
1759
+ const cells = {};
1760
+ const fields = this.model.view?.fields ?? [];
1761
+ const draftRows = this.model.draftRows.value;
1762
+ for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
1763
+ const draftRow = draftRows[row];
1549
1764
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1550
1765
  const field = fields[col];
1551
1766
  if (!field) {
1552
1767
  continue;
1553
1768
  }
1554
- addCell(this.model.rows.value[row], field, col, row);
1769
+ this.createDataCell(cells, draftRow.data, field, col, row);
1770
+ if (this.model.hasDraftRowValidationError(row, field.path)) {
1771
+ const cellIndex = toPlaneCellIndex({
1772
+ col,
1773
+ row
1774
+ });
1775
+ const cellValue = cells[cellIndex];
1776
+ if (cellValue) {
1777
+ const existingClasses = cellValue.className || "";
1778
+ const draftClasses = "dx-grid__cell--flagged";
1779
+ cellValue.className = existingClasses ? `${existingClasses} ${draftClasses}` : draftClasses;
1780
+ }
1781
+ }
1555
1782
  }
1556
1783
  }
1557
1784
  return cells;
@@ -1560,7 +1787,7 @@ var TablePresentation = class {
1560
1787
  const cells = {};
1561
1788
  const fields = this.model.view?.fields ?? [];
1562
1789
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1563
- const { field, props } = this.model.projection.getFieldProjection(fields[col].id);
1790
+ const { field, props } = this.getFieldProjection(fields[col].id);
1564
1791
  const sorting = this.model.sorting?.sorting;
1565
1792
  const direction = sorting?.fieldId === field.id ? sorting.direction : void 0;
1566
1793
  cells[toPlaneCellIndex({
@@ -1668,6 +1895,26 @@ var TablePresentation = class {
1668
1895
  }
1669
1896
  };
1670
1897
  }
1898
+ getDraftActionCells(range) {
1899
+ const cells = {};
1900
+ const draftRows = this.model.draftRows.value;
1901
+ for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
1902
+ const draftRow = draftRows[row];
1903
+ const disabled = !draftRow.valid;
1904
+ cells[toPlaneCellIndex({
1905
+ col: 0,
1906
+ row
1907
+ })] = {
1908
+ value: "",
1909
+ readonly: true,
1910
+ accessoryHtml: tableButtons.saveDraftRow.render({
1911
+ rowIndex: row,
1912
+ disabled
1913
+ })
1914
+ };
1915
+ }
1916
+ return cells;
1917
+ }
1671
1918
  };
1672
1919
  var cellClassesForRowSelection = (selected, selectionMode) => {
1673
1920
  if (!selected) {
@@ -1692,30 +1939,32 @@ var cellClassesForRowSelection = (selected, selectionMode) => {
1692
1939
  }
1693
1940
  };
1694
1941
 
1695
- // packages/ui/react-ui-table/src/components/TableCellEditor/TableCellEditor.tsx
1942
+ // src/components/TableCellEditor/TableCellEditor.tsx
1696
1943
  import { useSignals as _useSignals7 } from "@preact-signals/safe-react/tracking";
1697
- import React7, { useCallback as useCallback4, useMemo as useMemo4, useRef as useRef2, useState as useState2 } from "react";
1944
+ import React7, { useCallback as useCallback4, useEffect as useEffect3, useMemo as useMemo4, useRef as useRef2, useState as useState2 } from "react";
1698
1945
  import { FormatEnum as FormatEnum5, TypeEnum as TypeEnum4 } from "@dxos/echo-schema";
1699
1946
  import { invariant as invariant3 } from "@dxos/invariant";
1700
1947
  import { useThemeContext } from "@dxos/react-ui";
1701
1948
  import { createMarkdownExtensions } from "@dxos/react-ui-editor";
1702
1949
  import { cellQuery, editorKeys, parseCellIndex as parseCellIndex2, useGridContext as useGridContext4, GridCellEditor } from "@dxos/react-ui-grid";
1703
- import { tagPickerExtension, createLinks } from "@dxos/react-ui-tag-picker";
1950
+ import { tagPicker, createLinks } from "@dxos/react-ui-tag-picker";
1704
1951
 
1705
- // packages/ui/react-ui-table/src/components/TableCellEditor/CellValidationMessage.tsx
1952
+ // src/components/TableCellEditor/CellValidationMessage.tsx
1706
1953
  import { useSignals as _useSignals5 } from "@preact-signals/safe-react/tracking";
1707
1954
  import React5 from "react";
1708
1955
  import { useGridContext as useGridContext2 } from "@dxos/react-ui-grid";
1709
- var CellValidationMessage = ({ validationError, __gridScope }) => {
1956
+ var CellValidationMessage = ({ validationError, variant = "error", __gridScope }) => {
1710
1957
  var _effect = _useSignals5();
1711
1958
  try {
1712
1959
  const { editing, editBox: box } = useGridContext2("GridSheetCellEditor", __gridScope);
1713
1960
  if (!editing || !validationError) {
1714
1961
  return null;
1715
1962
  }
1963
+ const bgClass = variant === "error" ? "bg-errorSurface" : "bg-warningSurface";
1964
+ const textClass = variant === "error" ? "text-errorSurfaceText" : "text-warningSurfaceText";
1716
1965
  return /* @__PURE__ */ React5.createElement("div", {
1717
1966
  role: "none",
1718
- className: "absolute bg-errorSurface text-errorSurfaceText rounded-bs-sm text-xs p-1",
1967
+ className: `absolute ${bgClass} ${textClass} rounded-bs-sm text-xs p-1`,
1719
1968
  style: {
1720
1969
  ...{
1721
1970
  "--dx-gridCellWidth": `${box?.inlineSize ?? 200}px`
@@ -1731,7 +1980,7 @@ var CellValidationMessage = ({ validationError, __gridScope }) => {
1731
1980
  }
1732
1981
  };
1733
1982
 
1734
- // packages/ui/react-ui-table/src/components/TableCellEditor/FormCellEditor.tsx
1983
+ // src/components/TableCellEditor/FormCellEditor.tsx
1735
1984
  import { useSignals as _useSignals6 } from "@preact-signals/safe-react/tracking";
1736
1985
  import React6, { useCallback as useCallback3, useEffect as useEffect2, useMemo as useMemo3, useRef } from "react";
1737
1986
  import { getSnapshot as getSnapshot2 } from "@dxos/echo-schema";
@@ -1740,7 +1989,7 @@ import { Popover as Popover3 } from "@dxos/react-ui";
1740
1989
  import { Form as Form2 } from "@dxos/react-ui-form";
1741
1990
  import { parseCellIndex, useGridContext as useGridContext3 } from "@dxos/react-ui-grid";
1742
1991
  import { getDeep, setDeep } from "@dxos/util";
1743
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/components/TableCellEditor/FormCellEditor.tsx";
1992
+ var __dxlog_file3 = "/__w/dxos/dxos/packages/ui/react-ui-table/src/components/TableCellEditor/FormCellEditor.tsx";
1744
1993
  var FormCellEditor = ({ fieldProjection, model, schema, __gridScope }) => {
1745
1994
  var _effect = _useSignals6();
1746
1995
  try {
@@ -1832,7 +2081,7 @@ var FormCellEditor = ({ fieldProjection, model, schema, __gridScope }) => {
1832
2081
  }
1833
2082
  };
1834
2083
 
1835
- // packages/ui/react-ui-table/src/components/TableCellEditor/extension.ts
2084
+ // src/components/TableCellEditor/extension.ts
1836
2085
  import { autocompletion, pickedCompletion } from "@codemirror/autocomplete";
1837
2086
  import { EditorView } from "@codemirror/view";
1838
2087
  var completion = ({ debug, onQuery, onMatch }) => {
@@ -1871,8 +2120,8 @@ var completion = ({ debug, onQuery, onMatch }) => {
1871
2120
  ];
1872
2121
  };
1873
2122
 
1874
- // packages/ui/react-ui-table/src/components/TableCellEditor/TableCellEditor.tsx
1875
- var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/components/TableCellEditor/TableCellEditor.tsx";
2123
+ // src/components/TableCellEditor/TableCellEditor.tsx
2124
+ var __dxlog_file4 = "/__w/dxos/dxos/packages/ui/react-ui-table/src/components/TableCellEditor/TableCellEditor.tsx";
1876
2125
  var newValue = Symbol.for("newValue");
1877
2126
  var createOption = (text) => ({
1878
2127
  [newValue]: true,
@@ -1924,6 +2173,11 @@ var TableValueEditor = ({ model, modals, schema, onEnter, onFocus, onQuery, __gr
1924
2173
  _effect.f();
1925
2174
  }
1926
2175
  };
2176
+ var editorSlots = {
2177
+ scroller: {
2178
+ className: "!plb-[--dx-grid-cell-editor-padding-block]"
2179
+ }
2180
+ };
1927
2181
  var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }) => {
1928
2182
  var _effect = _useSignals7();
1929
2183
  try {
@@ -1931,6 +2185,7 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
1931
2185
  const suppressNextBlur = useRef2(false);
1932
2186
  const { themeMode } = useThemeContext();
1933
2187
  const [_validationError, setValidationError] = useState2(null);
2188
+ const [_validationVariant, setValidationVariant] = useState2("error");
1934
2189
  const fieldProjection = useMemo4(() => {
1935
2190
  if (!model || !editing) {
1936
2191
  return;
@@ -1940,7 +2195,7 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
1940
2195
  const fieldProjection2 = model.projection.getFieldProjection(field.id);
1941
2196
  invariant3(fieldProjection2, void 0, {
1942
2197
  F: __dxlog_file4,
1943
- L: 116,
2198
+ L: 119,
1944
2199
  S: void 0,
1945
2200
  A: [
1946
2201
  "fieldProjection",
@@ -1952,20 +2207,52 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
1952
2207
  model,
1953
2208
  editing
1954
2209
  ]);
2210
+ useEffect3(() => {
2211
+ if (!model || !editing || !fieldProjection) {
2212
+ setValidationError(null);
2213
+ return;
2214
+ }
2215
+ const cell = parseCellIndex2(editing.index);
2216
+ const { row, col } = cell;
2217
+ if (model.isDraftCell(cell)) {
2218
+ const field = model.projection.view.fields[col];
2219
+ const hasValidationError = model.hasDraftRowValidationError(row, field.path);
2220
+ if (hasValidationError) {
2221
+ const draftRows = model.draftRows.value;
2222
+ if (row >= 0 && row < draftRows.length) {
2223
+ const draftRow = draftRows[row];
2224
+ const validationError = draftRow.validationErrors?.find((error) => error.path === field.path);
2225
+ if (validationError) {
2226
+ setValidationError(validationError.message);
2227
+ setValidationVariant("warning");
2228
+ }
2229
+ }
2230
+ } else {
2231
+ setValidationError(null);
2232
+ }
2233
+ } else {
2234
+ setValidationError(null);
2235
+ }
2236
+ }, [
2237
+ model,
2238
+ editing,
2239
+ fieldProjection
2240
+ ]);
1955
2241
  const handleEnter = useCallback4(async (value) => {
1956
2242
  if (!model || !editing) {
1957
2243
  return;
1958
2244
  }
1959
2245
  const cell = parseCellIndex2(editing.index);
1960
- const result = await model.validateCellData(cell, value);
1961
- if (result.valid) {
2246
+ const validationResult = await model.validateCellData(cell, value);
2247
+ if (validationResult.valid) {
1962
2248
  setValidationError(null);
1963
2249
  model.setCellData(cell, value);
1964
2250
  onEnter?.(cell);
1965
2251
  onFocus?.();
1966
2252
  setEditing(null);
1967
2253
  } else {
1968
- setValidationError(result.error);
2254
+ setValidationError(validationResult.error);
2255
+ setValidationVariant("error");
1969
2256
  }
1970
2257
  }, [
1971
2258
  model,
@@ -2002,6 +2289,7 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
2002
2289
  }
2003
2290
  } else {
2004
2291
  setValidationError(result.error);
2292
+ setValidationVariant("error");
2005
2293
  }
2006
2294
  } else {
2007
2295
  setValidationError(null);
@@ -2037,9 +2325,9 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
2037
2325
  }));
2038
2326
  const options = fieldProjection.props.options || [];
2039
2327
  const mode = format === FormatEnum5.SingleSelect ? "single-select" : "multi-select";
2040
- extension2.push(tagPickerExtension({
2328
+ extension2.push(tagPicker({
2041
2329
  mode,
2042
- inGrid: true,
2330
+ keymap: false,
2043
2331
  onSearch: (text, selectedIds) => {
2044
2332
  return options.filter((option) => selectedIds.indexOf(option.id) === -1 && (text.length === 0 || option.title.toLowerCase().includes(text.toLowerCase()))).map((option) => ({
2045
2333
  id: option.id,
@@ -2109,8 +2397,8 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
2109
2397
  const value2 = model.getCellData(cell);
2110
2398
  if (value2 !== void 0) {
2111
2399
  const options = fieldProjection2.props.options || [];
2112
- if (fieldProjection2.props.format === FormatEnum5.MultiSelect && Array.isArray(value2)) {
2113
- const tagItems = value2.map((id) => {
2400
+ if (fieldProjection2.props.format === FormatEnum5.MultiSelect) {
2401
+ const tagItems = value2.split(",").map((id) => {
2114
2402
  const option = options.find((o) => o.id === id);
2115
2403
  if (option) {
2116
2404
  return {
@@ -2147,11 +2435,13 @@ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }
2147
2435
  ]);
2148
2436
  return /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(CellValidationMessage, {
2149
2437
  validationError: _validationError,
2438
+ variant: _validationVariant,
2150
2439
  __gridScope
2151
2440
  }), /* @__PURE__ */ React7.createElement(GridCellEditor, {
2152
2441
  extension,
2153
2442
  getCellContent,
2154
- onBlur: handleBlur
2443
+ onBlur: handleBlur,
2444
+ slots: editorSlots
2155
2445
  }));
2156
2446
  } finally {
2157
2447
  _effect.f();
@@ -2182,8 +2472,8 @@ var determineNavigationDelta = ({ key, shift }) => {
2182
2472
  return shift ? -1 : 1;
2183
2473
  };
2184
2474
 
2185
- // packages/ui/react-ui-table/src/components/Table/Table.tsx
2186
- var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/components/Table/Table.tsx";
2475
+ // src/components/Table/Table.tsx
2476
+ var __dxlog_file5 = "/__w/dxos/dxos/packages/ui/react-ui-table/src/components/Table/Table.tsx";
2187
2477
  var TableRoot = ({ children, role }) => {
2188
2478
  var _effect = _useSignals8();
2189
2479
  try {
@@ -2202,20 +2492,23 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
2202
2492
  const { hasAttention } = useAttention(model?.id ?? "table");
2203
2493
  const { t } = useTranslation3(translationKey);
2204
2494
  const modals = useMemo5(() => new ModalController(), []);
2495
+ const draftRowCount = model?.getDraftRowCount() ?? 0;
2205
2496
  const frozen = useMemo5(() => {
2206
2497
  const noActionColumn = model?.features.dataEditable === false && model?.features.schemaEditable === false && model.rowActions.length === 0;
2207
2498
  return {
2208
2499
  frozenRowsStart: 1,
2500
+ frozenRowsEnd: draftRowCount,
2209
2501
  frozenColsStart: model?.features.selection.enabled ? 1 : 0,
2210
2502
  frozenColsEnd: noActionColumn ? 0 : 1
2211
2503
  };
2212
2504
  }, [
2213
- model
2505
+ model,
2506
+ draftRowCount
2214
2507
  ]);
2215
2508
  const getCells = useCallback5((range, plane) => presentation?.getCells(range, plane) ?? {}, [
2216
2509
  presentation
2217
2510
  ]);
2218
- useEffect3(() => {
2511
+ useEffect4(() => {
2219
2512
  if (!presentation || !dxGrid) {
2220
2513
  return;
2221
2514
  }
@@ -2283,6 +2576,17 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
2283
2576
  model?.sorting?.toggleSort(data.fieldId);
2284
2577
  break;
2285
2578
  }
2579
+ case "saveDraftRow": {
2580
+ if (model) {
2581
+ const didCommitSuccessfully = model.commitDraftRow(data.rowIndex);
2582
+ if (dxGrid && didCommitSuccessfully) {
2583
+ requestAnimationFrame(() => {
2584
+ dxGrid.scrollToEndRow();
2585
+ });
2586
+ }
2587
+ }
2588
+ break;
2589
+ }
2286
2590
  }
2287
2591
  return;
2288
2592
  }
@@ -2316,7 +2620,8 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
2316
2620
  }
2317
2621
  }, [
2318
2622
  model,
2319
- modals
2623
+ modals,
2624
+ dxGrid
2320
2625
  ]);
2321
2626
  const handleFocus = useCallback5((increment, delta) => {
2322
2627
  if (dxGrid) {
@@ -2330,7 +2635,7 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
2330
2635
  return;
2331
2636
  }
2332
2637
  if (model && cell.row === model.getRowCount() - 1) {
2333
- model.insertRow(cell.row);
2638
+ model.insertRow();
2334
2639
  if (dxGrid) {
2335
2640
  requestAnimationFrame(() => {
2336
2641
  dxGrid?.scrollToRow(cell.row + 1);
@@ -2390,7 +2695,7 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
2390
2695
  const space = model.space;
2391
2696
  invariant4(space, void 0, {
2392
2697
  F: __dxlog_file5,
2393
- L: 311,
2698
+ L: 325,
2394
2699
  S: void 0,
2395
2700
  A: [
2396
2701
  "space",
@@ -2451,7 +2756,6 @@ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttenti
2451
2756
  }), /* @__PURE__ */ React8.createElement(Grid.Content, {
2452
2757
  className: mx2("[--dx-grid-base:var(--baseSurface)]", gridSeparatorInlineEnd, gridSeparatorBlockEnd),
2453
2758
  frozen,
2454
- // getCells={getCells}
2455
2759
  columns: model.columnMeta.value,
2456
2760
  limitRows: model.getRowCount() ?? 0,
2457
2761
  limitColumns: model.view?.fields?.length ?? 0,
@@ -2484,17 +2788,17 @@ var Table = {
2484
2788
  Main: TableMain
2485
2789
  };
2486
2790
 
2487
- // packages/ui/react-ui-table/src/components/Table/DynamicTable.tsx
2791
+ // src/components/Table/DynamicTable.tsx
2488
2792
  import { useSignals as _useSignals10 } from "@preact-signals/safe-react/tracking";
2489
- import React9, { useRef as useRef3, useMemo as useMemo6, useCallback as useCallback6 } from "react";
2793
+ import React9, { useRef as useRef3, useMemo as useMemo6, useCallback as useCallback7 } from "react";
2490
2794
  import { useDefaultValue } from "@dxos/react-ui";
2491
2795
  import { mx as mx3 } from "@dxos/react-ui-theme";
2492
2796
 
2493
- // packages/ui/react-ui-table/src/hooks/useTableModel.ts
2797
+ // src/hooks/useTableModel.ts
2494
2798
  import { useSignals as _useSignals9 } from "@preact-signals/safe-react/tracking";
2495
2799
  import { effect as effect3 } from "@preact/signals-core";
2496
2800
  import orderBy2 from "lodash.orderby";
2497
- import { useEffect as useEffect4, useState as useState4 } from "react";
2801
+ import { useEffect as useEffect5, useState as useState4 } from "react";
2498
2802
  import { fullyQualifiedId, getSpace } from "@dxos/react-client/echo";
2499
2803
  import { useSelectionActions } from "@dxos/react-ui-attention";
2500
2804
  import { isNonNullable } from "@dxos/util";
@@ -2502,7 +2806,7 @@ var useTableModel = ({ table, projection, rows, rowActions, features, onSelectio
2502
2806
  var _effect = _useSignals9();
2503
2807
  try {
2504
2808
  const [model, setModel] = useState4();
2505
- useEffect4(() => {
2809
+ useEffect5(() => {
2506
2810
  if (!table || !projection) {
2507
2811
  return;
2508
2812
  }
@@ -2532,7 +2836,7 @@ var useTableModel = ({ table, projection, rows, rowActions, features, onSelectio
2532
2836
  features,
2533
2837
  rowActions
2534
2838
  ]);
2535
- useEffect4(() => {
2839
+ useEffect5(() => {
2536
2840
  if (rows) {
2537
2841
  const sortedRows = orderBy2(rows, [
2538
2842
  (row) => String(row.id)
@@ -2549,7 +2853,7 @@ var useTableModel = ({ table, projection, rows, rowActions, features, onSelectio
2549
2853
  table?.id,
2550
2854
  table?.view?.target?.query.typename
2551
2855
  ].filter(isNonNullable));
2552
- useEffect4(() => {
2856
+ useEffect5(() => {
2553
2857
  if (!model) {
2554
2858
  return;
2555
2859
  }
@@ -2574,7 +2878,27 @@ var useTableModel = ({ table, projection, rows, rowActions, features, onSelectio
2574
2878
  }
2575
2879
  };
2576
2880
 
2577
- // packages/ui/react-ui-table/src/components/Table/DynamicTable.tsx
2881
+ // src/hooks/useAddRow.ts
2882
+ import { useCallback as useCallback6 } from "react";
2883
+ import { Obj as Obj3 } from "@dxos/echo";
2884
+ var useAddRow = ({ space, schema }) => {
2885
+ return useCallback6((data) => {
2886
+ if (space && schema) {
2887
+ try {
2888
+ space.db.add(Obj3.make(schema, data ?? {}));
2889
+ return true;
2890
+ } catch (error) {
2891
+ return false;
2892
+ }
2893
+ }
2894
+ return false;
2895
+ }, [
2896
+ space,
2897
+ schema
2898
+ ]);
2899
+ };
2900
+
2901
+ // src/components/Table/DynamicTable.tsx
2578
2902
  var DynamicTable = ({ classNames, name = "example.com/dynamic-table", rows, properties, jsonSchema, schema, rowActions, onRowClick, onRowAction, ...props }) => {
2579
2903
  var _effect = _useSignals10();
2580
2904
  try {
@@ -2596,10 +2920,10 @@ var DynamicTable = ({ classNames, name = "example.com/dynamic-table", rows, prop
2596
2920
  jsonSchema
2597
2921
  ]);
2598
2922
  const tableRef = useRef3(null);
2599
- const handleCellUpdate = useCallback6((cell) => {
2923
+ const handleCellUpdate = useCallback7((cell) => {
2600
2924
  tableRef.current?.update?.(cell);
2601
2925
  }, []);
2602
- const handleRowOrderChange = useCallback6(() => {
2926
+ const handleRowOrderChange = useCallback7(() => {
2603
2927
  tableRef.current?.update?.();
2604
2928
  }, []);
2605
2929
  const features = useDefaultValue(props.features, () => ({
@@ -2643,7 +2967,7 @@ var DynamicTable = ({ classNames, name = "example.com/dynamic-table", rows, prop
2643
2967
  }
2644
2968
  };
2645
2969
 
2646
- // packages/ui/react-ui-table/src/components/TableToolbar/TableToolbar.tsx
2970
+ // src/components/TableToolbar/TableToolbar.tsx
2647
2971
  import { useSignals as _useSignals11 } from "@preact-signals/safe-react/tracking";
2648
2972
  import { Rx } from "@effect-rx/rx-react";
2649
2973
  import React10, { useMemo as useMemo7 } from "react";
@@ -2744,7 +3068,8 @@ export {
2744
3068
  tableButtons,
2745
3069
  tableControls,
2746
3070
  touch,
2747
- translations_default as translations,
3071
+ translations,
3072
+ useAddRow,
2748
3073
  useTableModel
2749
3074
  };
2750
3075
  //# sourceMappingURL=index.mjs.map