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