@iyulab/flex-table 0.56.0 → 0.57.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.57.0] - 2026-10-07
4
+
5
+ ### Changed (breaking)
6
+
7
+ - **`getSelectedRows()` returns the selected rows** (`DataRow[]` — the rows `data` holds, in `data` order), the same
8
+ shape as `u-rich-table`'s `getSelectedRows()`. It returned `{ selectedIds, selectedIndices, selectedRows }`, so code
9
+ written for one table read `undefined` from the other (`getSelectedRows().length`). Every selected id, on every page,
10
+ is `selectedRowIds`; positions in `data` come with `selection-change` (`selectedIndices`), whose detail is unchanged.
11
+
12
+ **Migrating**: `getSelectedRows().selectedRows` → `getSelectedRows()` · `.selectedIds` → `[...selectedRowIds]` ·
13
+ `.selectedIndices` → the `selection-change` detail, or `getSelectedRows().map((r) => table.data.indexOf(r))`.
14
+
15
+ ## [0.56.1] - 2026-10-07
16
+
17
+ ### Fixed
18
+
19
+ - **An edit is written to the row it was started on.** The open editor remembered a screen position and the commit
20
+ wrote whatever row stood there, so a refresh that put a row above the one being edited (or a sort while editing)
21
+ sent the typed value into another row. The editor now follows its row — drawn again in the row's new place with
22
+ what was typed and the focus — and an edit whose row left `data` (or a filter hid) is cancelled.
23
+ - **Undo and redo write the rows they changed** (cell edits, `updateRows`, paste, clear, fill, replace). They wrote
24
+ `data` positions, so after `data` changed in between they changed other rows.
25
+ - **Comments stay on their rows.** They were kept by `data` index, so inserting or deleting a row moved every comment
26
+ below it onto the neighbouring row. A deleted row's comments leave with it (and come back on undo).
27
+ - **Validation marks stay on their cells** when the view reorders while one is shown.
28
+ - `cell-edit-cancel` reports `row` as the data index, like the other edit events (it was the screen position) —
29
+ `-1` when the row is no longer in `data`.
30
+
31
+ ### Added
32
+
33
+ - `comment-change` and `getAllComments()` carry the row's `id`.
34
+
3
35
  ## [0.56.0] - 2026-10-07
4
36
 
5
37
  ### Fixed
package/README.md CHANGED
@@ -258,7 +258,7 @@ Every row keeps its own value. Sorting, filtering, copying, CSV/XLSX export and
258
258
  | `deselectAll()` | `void` | Deselect every row, on every page |
259
259
  | `selectWhere(predicate)` | `void` | Add the rows in view for which `predicate(row, dataIndex)` is true |
260
260
  | `setSelection(ids)` | `void` | Replace the selection with these row ids. The same set again does nothing and fires nothing |
261
- | `getSelectedRows()` | `{ selectedIds, selectedIndices, selectedRows }` | `selectedIds`: every selected row (all pages). `selectedRows` / `selectedIndices`: the selected rows `data` holds, and their positions in it |
261
+ | `getSelectedRows()` | `DataRow[]` | The selected rows `data` holds, in `data` order — the same shape as `u-rich-table`'s. Every selected id (all pages) is `selectedRowIds`; positions come with `selection-change` (`selectedIndices`) |
262
262
  | `getRowId(row)` | `string` | The id selection keeps for a row |
263
263
  | `selectedRowIds` (getter) | `ReadonlySet<string>` | Ids of the selected rows, all pages (a copy) |
264
264
 
@@ -268,7 +268,7 @@ Row selection is kept by **row id**, not by position, so a checkmark stays on it
268
268
  table.rowKey = 'orderNo'; // or: table.rowKey = (row) => `${row.site}/${row.no}`
269
269
  ```
270
270
 
271
- A keyed row stays selected when `data` is replaced, so in a server-paged grid the selection spans pages (`selectedIds` names them all; `selectedRows` holds the ones on this page). A row without a key is named by the row object itself (a session-local id `#n`): it follows the row while the same object is in `data` and leaves the selection when it is gone. Set `clear-selection-on-data-change` when every new `data` should start a new selection (a new search):
271
+ A keyed row stays selected when `data` is replaced, so in a server-paged grid the selection spans pages (`selectedRowIds` names them all; `getSelectedRows()` holds the ones on this page). A row without a key is named by the row object itself (a session-local id `#n`): it follows the row while the same object is in `data` and leaves the selection when it is gone. Set `clear-selection-on-data-change` when every new `data` should start a new selection (a new search):
272
272
 
273
273
  ```html
274
274
  <flex-table selectable clear-selection-on-data-change></flex-table>
@@ -306,7 +306,7 @@ type-checks without a cast. The React wrapper's `on*` props carry the same types
306
306
  | `cell-select` | `{ row, col }` or `null` | Cell focus changed (`null` when no cell is active) |
307
307
  | `cell-edit-start` | `{ row, col, key, value }` | Cell editing started |
308
308
  | `cell-edit-commit` | `{ row, col, key, oldValue, newValue }` | Cell value committed |
309
- | `cell-edit-cancel` | `{ row, col }` | Cell edit cancelled (Escape) |
309
+ | `cell-edit-cancel` | `{ row, col }` | Cell edit cancelled (Escape, or its row left `data`). `row` is the data index (`-1` once the row is gone) |
310
310
  | `validation-error` | `{ row, col, key, value, error }` | Cell validator rejected value, or a `number` cell got text that is not a number |
311
311
 
312
312
  ### Data Events
@@ -321,7 +321,7 @@ type-checks without a cast. The React wrapper's `on*` props carry the same types
321
321
  | `data-import` | `{ count }` | Rows imported from a file |
322
322
  | `fill-handle-apply` | `{ sourceRange, targetRange, cells }` | Fill handle wrote `cells` (`{ dataRow, key, oldValue, newValue }`) |
323
323
  | `find-replace` | `{ type, cells }` | Replace (`type: 'replace'`) or replace-all from the find panel; `cells` are `{ row, col, oldValue, newValue }` with `col` the column key |
324
- | `comment-change` | `{ dataIndex, colKey, text }` | Cell comment set, changed or removed (`text: null`) |
324
+ | `comment-change` | `{ dataIndex, id, colKey, text }` | Cell comment set, changed or removed (`text: null`). Comments stay on their rows (`id`) when rows move |
325
325
 
326
326
  ### Column Events
327
327
 
@@ -1,14 +1,20 @@
1
1
  import type { CellPosition } from './selection.js';
2
+ import type { DataRow } from '../models/types.js';
2
3
  export interface EditState {
4
+ /** Where the editor is drawn — the visual cell. The grid moves it when the view moves the row. */
3
5
  position: CellPosition;
4
6
  originalValue: unknown;
7
+ /** The row being edited — the commit writes it, wherever `data` has moved it. */
8
+ row: DataRow;
9
+ /** What the editor held when the row moved and the editor was drawn again — handed to the new one. */
10
+ draft?: unknown;
5
11
  }
6
12
  /**
7
13
  * Manages cell editing state.
8
14
  */
9
15
  export declare class EditingState {
10
16
  current: EditState | null;
11
- start(position: CellPosition, originalValue: unknown): void;
17
+ start(position: CellPosition, originalValue: unknown, row: DataRow): void;
12
18
  isEditing(row: number, col: number): boolean;
13
19
  cancel(): EditState | null;
14
20
  commit(): EditState | null;
package/dist/events.d.ts CHANGED
@@ -126,8 +126,10 @@ export interface FlexTableEventMap {
126
126
  value: unknown;
127
127
  error: string;
128
128
  }>;
129
+ /** `id` — the row's id (comments stay on their rows); `dataIndex` — its position in `data` now (-1 when not loaded). */
129
130
  'comment-change': CustomEvent<{
130
131
  dataIndex: number;
132
+ id: string;
131
133
  colKey: string;
132
134
  text: string | null;
133
135
  }>;
@@ -1059,25 +1059,25 @@ function E(e, t) {
1059
1059
  //#endregion
1060
1060
  //#region src/renderers/cell-renderer.ts
1061
1061
  function D(e, t, n) {
1062
- return n.render ? n.render(e, t, n) : n.format ? typeof n.format == "function" ? n.format(e, t, n) : C(e, n.format) : O(e, n);
1062
+ return n.render ? n.render(e, t, n) : n.format ? typeof n.format == "function" ? n.format(e, t, n) : C(e, n.format) : ee(e, n);
1063
1063
  }
1064
- function O(e, t) {
1064
+ function ee(e, t) {
1065
1065
  if (e == null) return "";
1066
1066
  switch (t.type) {
1067
- case "number": return ne(e);
1067
+ case "number": return re(e);
1068
1068
  case "boolean": return a`<span class="ft-bool">${e ? "✔" : ""}</span>`;
1069
- case "date": return re(e);
1070
- case "datetime": return ie(e);
1071
- case "select": return ee(e, t);
1069
+ case "date": return ie(e);
1070
+ case "datetime": return ae(e);
1071
+ case "select": return te(e, t);
1072
1072
  default: return String(e);
1073
1073
  }
1074
1074
  }
1075
- function ee(e, t) {
1075
+ function te(e, t) {
1076
1076
  if (!t.options || t.options.length === 0 || typeof t.options[0] == "string") return e == null ? "" : String(e);
1077
1077
  let n = t.options.find((t) => t.value === e);
1078
1078
  return n ? n.label : e == null ? "" : String(e);
1079
1079
  }
1080
- var te = {
1080
+ var ne = {
1081
1081
  year: "numeric",
1082
1082
  month: "numeric",
1083
1083
  day: "numeric",
@@ -1085,18 +1085,18 @@ var te = {
1085
1085
  minute: "numeric",
1086
1086
  second: "numeric"
1087
1087
  };
1088
- function ne(e) {
1088
+ function re(e) {
1089
1089
  return typeof e == "number" ? Number.isInteger(e) ? f(e) : f(e, { minimumFractionDigits: 1 }) : String(e);
1090
1090
  }
1091
- function re(e) {
1091
+ function ie(e) {
1092
1092
  return e instanceof Date || typeof e == "string" ? d(e) : String(e);
1093
1093
  }
1094
- function ie(e) {
1095
- return e instanceof Date || typeof e == "string" ? d(e, te) : String(e);
1094
+ function ae(e) {
1095
+ return e instanceof Date || typeof e == "string" ? d(e, ne) : String(e);
1096
1096
  }
1097
1097
  //#endregion
1098
1098
  //#region src/core/selection.ts
1099
- function ae(e) {
1099
+ function oe(e) {
1100
1100
  return {
1101
1101
  startRow: Math.min(e.startRow, e.endRow),
1102
1102
  startCol: Math.min(e.startCol, e.endCol),
@@ -1104,7 +1104,7 @@ function ae(e) {
1104
1104
  endCol: Math.max(e.startCol, e.endCol)
1105
1105
  };
1106
1106
  }
1107
- var oe = class {
1107
+ var se = class {
1108
1108
  constructor() {
1109
1109
  this.activeCell = null, this.rangeAnchor = null, this.range = null, this.extraRanges = [], this._rowCount = 0, this._colCount = 0;
1110
1110
  }
@@ -1131,7 +1131,7 @@ var oe = class {
1131
1131
  return e < 0 || e >= this._rowCount || t < 0 || t >= this._colCount ? this.activeCell : (!this.rangeAnchor && this.activeCell && (this.rangeAnchor = { ...this.activeCell }), this.activeCell = {
1132
1132
  row: e,
1133
1133
  col: t
1134
- }, this.rangeAnchor && (this.range = ae({
1134
+ }, this.rangeAnchor && (this.range = oe({
1135
1135
  startRow: this.rangeAnchor.row,
1136
1136
  startCol: this.rangeAnchor.col,
1137
1137
  endRow: e,
@@ -1202,14 +1202,15 @@ var oe = class {
1202
1202
  shiftMoveRight() {
1203
1203
  return this.activeCell ? this.setActiveWithRange(this.activeCell.row, this.activeCell.col + 1) : null;
1204
1204
  }
1205
- }, se = class {
1205
+ }, ce = class {
1206
1206
  constructor() {
1207
1207
  this.current = null;
1208
1208
  }
1209
- start(e, t) {
1209
+ start(e, t, n) {
1210
1210
  this.current = {
1211
1211
  position: e,
1212
- originalValue: t
1212
+ originalValue: t,
1213
+ row: n
1213
1214
  };
1214
1215
  }
1215
1216
  isEditing(e, t) {
@@ -1223,7 +1224,7 @@ var oe = class {
1223
1224
  let e = this.current;
1224
1225
  return this.current = null, e;
1225
1226
  }
1226
- }, k = class {
1227
+ }, O = class {
1227
1228
  constructor() {
1228
1229
  this._selected = /* @__PURE__ */ new Set(), this._mode = "multi";
1229
1230
  }
@@ -1290,7 +1291,7 @@ var oe = class {
1290
1291
  };
1291
1292
  //#endregion
1292
1293
  //#region src/core/filtering.ts
1293
- function ce(e, t, n) {
1294
+ function k(e, t, n) {
1294
1295
  if (t.length === 0) return Array.from({ length: e.length }, (e, t) => t);
1295
1296
  let r = [];
1296
1297
  for (let i = 0; i < e.length; i++) {
@@ -1762,7 +1763,9 @@ var K = {
1762
1763
  lte: "≤"
1763
1764
  }, q = 120, J = 40, Y = 20, X = 32, Z = 5, Xe = 200, Q = class extends r {
1764
1765
  constructor(...e) {
1765
- super(...e), this.columns = [], this._data = [], this._inUndoRedo = !1, this.clearUndoOnDataChange = !1, this.clearSelectionOnDataChange = !1, this.rowKey = "_id", this._rowHeightFromCss = X, this._targetFloor = 0, this.showRowNumbers = !1, this.theme = void 0, this.maxRows = 0, this.editable = !0, this.showFilters = !1, this.showContextMenu = !1, this.frozenRows = 0, this.emptyMessage = "", this.noMatchingMessage = "", this.error = null, this.loading = !1, this.importEnabled = !1, this.selectable = !1, this.dataMode = "client", this.footerData = null, this.stylesheets = [], this._scrollTop = 0, this._scrollLeft = 0, this._viewportHeight = 0, this._viewportWidth = 0, this._colLeftOffsets = [], this._totalRowWidth = 0, this._baseStyleSheets = [], this._activeCell = null, this._headerCol = null, this._focusedCellEl = null, this._editingCell = null, this._sortCriteria = [], this._selection = new oe(), this._editing = new se(), this._rowSelection = new k(), this._undo = new A(), this._filters = [], this._filteredIndices = [], this._sortedIndices = [], this._openFilterKey = null, this._rowSelectionVersion = 0, this._lastCheckboxRowId = null, this._unkeyedIds = /* @__PURE__ */ new WeakMap(), this._unkeyedSeq = 0, this._visibleIdsCache = null, this._checkboxShiftPending = !1, this._autocompleteState = null, this._headerMenu = null, this._bodyContextMenu = null, this._commentPopup = null, this._viewDirty = !0, this._isDragging = !1, this._wasDrag = !1, this._resizing = null, this._resizeCleanup = null, this._wasHeaderDrag = !1, this._wasRowDrag = !1, this._colDrag = null, this._colDragIndicatorLeft = null, this._fillDrag = null, this._rowDrag = null, this._rowDragIndicatorY = null, this._findState = null, this._columnWidths = /* @__PURE__ */ new Map(), this._hostResizeObserver = null, this._comments = /* @__PURE__ */ new Map(), this._isDragOver = !1, this._warnedUnconstrained = !1, this._onHostFocus = (e) => {
1766
+ super(...e), this.columns = [], this._data = [], this._inUndoRedo = !1, this.clearUndoOnDataChange = !1, this.clearSelectionOnDataChange = !1, this.rowKey = "_id", this._rowHeightFromCss = X, this._targetFloor = 0, this.showRowNumbers = !1, this.theme = void 0, this.maxRows = 0, this.editable = !0, this.showFilters = !1, this.showContextMenu = !1, this.frozenRows = 0, this.emptyMessage = "", this.noMatchingMessage = "", this.error = null, this.loading = !1, this.importEnabled = !1, this.selectable = !1, this.dataMode = "client", this.footerData = null, this.stylesheets = [], this._scrollTop = 0, this._scrollLeft = 0, this._viewportHeight = 0, this._viewportWidth = 0, this._colLeftOffsets = [], this._totalRowWidth = 0, this._baseStyleSheets = [], this._activeCell = null, this._headerCol = null, this._focusedCellEl = null, this._editingCell = null, this._sortCriteria = [], this._selection = new se(), this._editing = new ce(), this._rowSelection = new O(), this._undo = new A(), this._filters = [], this._filteredIndices = [], this._sortedIndices = [], this._openFilterKey = null, this._rowSelectionVersion = 0, this._lastCheckboxRowId = null, this._unkeyedIds = /* @__PURE__ */ new WeakMap(), this._unkeyedSeq = 0, this._visibleIdsCache = null, this._checkboxShiftPending = !1, this._autocompleteState = null, this._headerMenu = null, this._bodyContextMenu = null, this._commentPopup = null, this._viewDirty = !0, this._isDragging = !1, this._wasDrag = !1, this._resizing = null, this._resizeCleanup = null, this._wasHeaderDrag = !1, this._wasRowDrag = !1, this._colDrag = null, this._colDragIndicatorLeft = null, this._fillDrag = null, this._rowDrag = null, this._rowDragIndicatorY = null, this._findState = null, this._columnWidths = /* @__PURE__ */ new Map(), this._hostResizeObserver = null, this._comments = /* @__PURE__ */ new Map(), this._isDragOver = !1, this._editorMoving = !1, this._onEditorBlur = (e) => {
1767
+ !this._editorMoving && e.currentTarget?.isConnected && this._commitEdit();
1768
+ }, this._warnedUnconstrained = !1, this._onHostFocus = (e) => {
1766
1769
  e.composedPath()[0] !== this || this._activeCell || this._headerCol !== null || this.visibleColumns.length !== 0 && this.matches(":focus-visible") && (this._visibleRowCount > 0 ? (this._selection.setActive(0, 0), this._syncActiveCell()) : this._headerCol = 0);
1767
1770
  }, this._onFocusIn = (e) => {
1768
1771
  let t = e.composedPath()[0];
@@ -1790,7 +1793,7 @@ var K = {
1790
1793
  }
1791
1794
  set data(e) {
1792
1795
  let t = this._data, n = e !== t;
1793
- this._data = e, n && !this._inUndoRedo && (this.clearUndoOnDataChange && this._undo.clear(), this._rowSelection.selectedCount > 0 && (this.clearSelectionOnDataChange ? (this._rowSelection.deselectAll(), 1) : this._pruneGoneUnkeyedRows()) && (this._rowSelectionVersion++, this._dispatchRowSelectionEvent())), this.requestUpdate("data", t);
1796
+ this._data = e, n && !this._inUndoRedo && (this.clearUndoOnDataChange && this._undo.clear(), this._comments.size > 0 && this._pruneGoneUnkeyedComments(), this._rowSelection.selectedCount > 0 && (this.clearSelectionOnDataChange ? (this._rowSelection.deselectAll(), 1) : this._pruneGoneUnkeyedRows()) && (this._rowSelectionVersion++, this._dispatchRowSelectionEvent())), this.requestUpdate("data", t);
1794
1797
  }
1795
1798
  get rowHeight() {
1796
1799
  let e = this._rowHeightExplicit ?? this._rowHeightFromCss ?? X;
@@ -1841,6 +1844,9 @@ var K = {
1841
1844
  return this._filteredIndices.length;
1842
1845
  }
1843
1846
  getSelectedRows() {
1847
+ return this._selectionSnapshot().selectedRows;
1848
+ }
1849
+ _selectionSnapshot() {
1844
1850
  let e = [];
1845
1851
  if (this._rowSelection.selectedCount > 0) for (let t = 0; t < this.data.length; t++) this._rowSelection.isSelected(this.getRowId(this.data[t])) && e.push(t);
1846
1852
  return {
@@ -1877,7 +1883,7 @@ var K = {
1877
1883
  }
1878
1884
  }
1879
1885
  _dispatchRowSelectionEvent() {
1880
- this._emit("selection-change", this.getSelectedRows());
1886
+ this._emit("selection-change", this._selectionSnapshot());
1881
1887
  }
1882
1888
  _visibleRowIds() {
1883
1889
  let e = this._visibleIdsCache;
@@ -1894,6 +1900,11 @@ var K = {
1894
1900
  ids: t
1895
1901
  }, t;
1896
1902
  }
1903
+ _pruneGoneUnkeyedComments() {
1904
+ if (![...this._comments.keys()].some((e) => e.startsWith("#"))) return;
1905
+ let e = new Set(this.data.map((e) => this.getRowId(e)));
1906
+ for (let t of [...this._comments.keys()]) t.startsWith("#") && !e.has(t) && this._comments.delete(t);
1907
+ }
1897
1908
  _pruneGoneUnkeyedRows() {
1898
1909
  if (!this._rowSelection.selectedIds.some((e) => e.startsWith("#"))) return !1;
1899
1910
  let e = new Set(this.data.map((e) => this.getRowId(e)));
@@ -2073,7 +2084,7 @@ var K = {
2073
2084
  }), this._dispatchUndoStateEvent(), n;
2074
2085
  }
2075
2086
  deleteRows(e) {
2076
- let t = e ?? (this.selectable ? this.getSelectedRows().selectedIndices : this._getSelectedDataRows());
2087
+ let t = e ?? (this.selectable ? this._selectionSnapshot().selectedIndices : this._getSelectedDataRows());
2077
2088
  if (t.length === 0) return;
2078
2089
  let n = [...t].sort((e, t) => t - e), r = [];
2079
2090
  for (let e of n) e >= 0 && e < this.data.length && (r.push({
@@ -2081,20 +2092,28 @@ var K = {
2081
2092
  row: this.data[e]
2082
2093
  }), this.data.splice(e, 1));
2083
2094
  if (r.length === 0) return;
2084
- r.reverse(), this._undo.push({
2095
+ r.reverse();
2096
+ let i = /* @__PURE__ */ new Map();
2097
+ for (let { row: e } of r) {
2098
+ let t = this.getRowId(e), n = this._comments.get(t);
2099
+ n && (i.set(t, n), this._comments.delete(t));
2100
+ }
2101
+ this._undo.push({
2085
2102
  label: "row-delete",
2086
2103
  undo: () => {
2087
2104
  for (let { index: e, row: t } of r) this.data.splice(e, 0, t);
2105
+ for (let [e, t] of i) this._comments.set(e, t);
2088
2106
  this.requestUpdate();
2089
2107
  },
2090
2108
  redo: () => {
2091
2109
  let e = [...r].reverse();
2092
2110
  for (let { index: t } of e) this.data.splice(t, 1);
2111
+ for (let e of i.keys()) this._comments.delete(e);
2093
2112
  this.requestUpdate();
2094
2113
  }
2095
2114
  });
2096
- let i = r.map((e) => this.getRowId(e.row)).filter((e) => this._rowSelection.isSelected(e));
2097
- if (i.length > 0 && (this._rowSelection.deselectMany(i), this._rowSelectionVersion++, this._dispatchRowSelectionEvent()), this._activeCell) {
2115
+ let a = r.map((e) => this.getRowId(e.row)).filter((e) => this._rowSelection.isSelected(e));
2116
+ if (a.length > 0 && (this._rowSelection.deselectMany(a), this._rowSelectionVersion++, this._dispatchRowSelectionEvent()), this._activeCell) {
2098
2117
  let e = this._toDataIndex(this._activeCell.row);
2099
2118
  t.includes(e) && (this._selection.clear(), this._activeCell = null);
2100
2119
  }
@@ -2105,26 +2124,28 @@ var K = {
2105
2124
  }
2106
2125
  updateRows(e) {
2107
2126
  if (e.length === 0) return;
2108
- let t = [];
2109
- for (let n of e) {
2110
- if (n.row < 0 || n.row >= this.data.length) continue;
2111
- let e = this.data[n.row][n.key];
2112
- this.data[n.row][n.key] = n.value, t.push({
2113
- row: n.row,
2114
- key: n.key,
2127
+ let t = [], n = [];
2128
+ for (let r of e) {
2129
+ if (r.row < 0 || r.row >= this.data.length) continue;
2130
+ let e = this.data[r.row][r.key];
2131
+ this.data[r.row][r.key] = r.value, t.push({
2132
+ row: r.row,
2133
+ key: r.key,
2115
2134
  oldValue: e,
2116
- newValue: n.value
2117
- });
2135
+ newValue: r.value
2136
+ }), n.push(this.data[r.row]);
2118
2137
  }
2119
2138
  t.length !== 0 && (this._undo.push({
2120
2139
  label: "batch-update",
2121
2140
  undo: () => {
2122
- for (let e of t) this.data[e.row][e.key] = e.oldValue;
2123
- this.requestUpdate();
2141
+ t.forEach((e, t) => {
2142
+ n[t][e.key] = e.oldValue;
2143
+ }), this.requestUpdate();
2124
2144
  },
2125
2145
  redo: () => {
2126
- for (let e of t) this.data[e.row][e.key] = e.newValue;
2127
- this.requestUpdate();
2146
+ t.forEach((e, t) => {
2147
+ n[t][e.key] = e.newValue;
2148
+ }), this.requestUpdate();
2128
2149
  }
2129
2150
  }), this.requestUpdate(), this._emit("batch-update", { changes: t }), this._dispatchUndoStateEvent());
2130
2151
  }
@@ -2172,14 +2193,16 @@ var K = {
2172
2193
  };
2173
2194
  }
2174
2195
  setComment(e, t, n) {
2175
- let r = this.getComment(e, t);
2176
- this._applyComment(e, t, n), this._undo.push({
2196
+ let r = this.data[e];
2197
+ if (!r) return;
2198
+ let i = this.getRowId(r), a = this._comments.get(i)?.get(t) ?? null;
2199
+ this._applyComment(i, t, n), this._undo.push({
2177
2200
  label: "comment",
2178
2201
  undo: () => {
2179
- this._applyComment(e, t, r);
2202
+ this._applyComment(i, t, a);
2180
2203
  },
2181
2204
  redo: () => {
2182
- this._applyComment(e, t, n);
2205
+ this._applyComment(i, t, n);
2183
2206
  }
2184
2207
  });
2185
2208
  }
@@ -2189,23 +2212,29 @@ var K = {
2189
2212
  let n = this._comments.get(e);
2190
2213
  n && (n.delete(t), n.size === 0 && this._comments.delete(e));
2191
2214
  }
2215
+ let r = this.data.findIndex((t) => this.getRowId(t) === e);
2192
2216
  this._emit("comment-change", {
2193
- dataIndex: e,
2217
+ dataIndex: r,
2218
+ id: e,
2194
2219
  colKey: t,
2195
2220
  text: n ?? null
2196
2221
  }), this.requestUpdate();
2197
2222
  }
2198
2223
  getComment(e, t) {
2199
- return this._comments.get(e)?.get(t) ?? null;
2224
+ let n = this.data[e];
2225
+ return n ? this._comments.get(this.getRowId(n))?.get(t) ?? null : null;
2200
2226
  }
2201
2227
  getAllComments() {
2202
2228
  let e = [];
2203
- for (let [t, n] of this._comments) for (let [r, i] of n) e.push({
2204
- dataIndex: t,
2205
- colKey: r,
2206
- text: i
2207
- });
2208
- return e;
2229
+ return this._comments.size === 0 || this.data.forEach((t, n) => {
2230
+ let r = this.getRowId(t);
2231
+ for (let [t, i] of this._comments.get(r) ?? []) e.push({
2232
+ dataIndex: n,
2233
+ id: r,
2234
+ colKey: t,
2235
+ text: i
2236
+ });
2237
+ }), e;
2209
2238
  }
2210
2239
  clearComments() {
2211
2240
  let e = new Map([...this._comments].map(([e, t]) => [e, new Map(t)]));
@@ -2391,10 +2420,10 @@ var K = {
2391
2420
  };
2392
2421
  }
2393
2422
  willUpdate(e) {
2394
- (!(e.size <= 2 && !e.has("data") && !e.has("columns") && !e.has("_openFilterKey") && !e.has("_editingCell") && !e.has("_activeCell") && (e.has("_scrollTop") || e.has("_scrollLeft"))) || this._viewDirty) && (this._recomputeView(), this._viewDirty = !1), this._updateColOffsets(), this._selection.setDimensions(this._visibleRowCount, this.visibleColumns.length);
2423
+ (!(e.size <= 2 && !e.has("data") && !e.has("columns") && !e.has("_openFilterKey") && !e.has("_editingCell") && !e.has("_activeCell") && (e.has("_scrollTop") || e.has("_scrollLeft"))) || this._viewDirty) && (this._recomputeView(), this._viewDirty = !1, this._followEditingRow()), this._updateColOffsets(), this._selection.setDimensions(this._visibleRowCount, this.visibleColumns.length);
2395
2424
  }
2396
2425
  updated(e) {
2397
- this._focusEditor(), this._syncGridFocus(), this._adjustFilterDropdown(), e.has("_headerMenu") && this._keepMenuInView(".ft-header-menu"), e.has("_bodyContextMenu") && this._bodyContextMenu && (this._keepMenuInView(".ft-body-context-menu"), this.shadowRoot?.querySelector(".ft-body-context-menu [role=\"menuitem\"]")?.focus()), e.has("stylesheets") && this._syncStylesheets();
2426
+ this._focusEditor(), this._editorMoving = !1, this._syncGridFocus(), this._adjustFilterDropdown(), e.has("_headerMenu") && this._keepMenuInView(".ft-header-menu"), e.has("_bodyContextMenu") && this._bodyContextMenu && (this._keepMenuInView(".ft-body-context-menu"), this.shadowRoot?.querySelector(".ft-body-context-menu [role=\"menuitem\"]")?.focus()), e.has("stylesheets") && this._syncStylesheets();
2398
2427
  let t = String(1 + this._visibleRowCount + +!!this.footerData), n = String(this.visibleColumns.length);
2399
2428
  if (this.getAttribute("aria-rowcount") !== t && this.setAttribute("aria-rowcount", t), this.getAttribute("aria-colcount") !== n && this.setAttribute("aria-colcount", n), this.loading ? this.getAttribute("aria-busy") !== "true" && this.setAttribute("aria-busy", "true") : this.hasAttribute("aria-busy") && this.removeAttribute("aria-busy"), e.has("_commentPopup") && this._commentPopup) {
2400
2429
  let e = this.shadowRoot?.querySelector(".ft-comment-popup textarea");
@@ -2406,7 +2435,7 @@ var K = {
2406
2435
  this._filteredIndices = Array.from({ length: this.data.length }, (e, t) => t), this._sortedIndices = this._filteredIndices;
2407
2436
  return;
2408
2437
  }
2409
- if (this._filteredIndices = ce(this.data, this._filters, (e, t, n) => {
2438
+ if (this._filteredIndices = k(this.data, this._filters, (e, t, n) => {
2410
2439
  this._emit("filter-error", {
2411
2440
  error: e,
2412
2441
  row: t,
@@ -2419,12 +2448,32 @@ var K = {
2419
2448
  this._sortedIndices = t.map((e) => this._filteredIndices[e]);
2420
2449
  }
2421
2450
  }
2451
+ _followEditingRow() {
2452
+ let e = this._editing.current;
2453
+ if (!e) return;
2454
+ let t = this.data.indexOf(e.row), n = t < 0 ? -1 : this._sortedIndices.indexOf(t);
2455
+ if (n < 0) {
2456
+ this._cancelEdit();
2457
+ return;
2458
+ }
2459
+ if (n === e.position.row) return;
2460
+ let r = this.shadowRoot?.querySelector(".ft-editor");
2461
+ r && "value" in r && (e.draft = r.value), this._editorMoving = !0, e.position = {
2462
+ row: n,
2463
+ col: e.position.col
2464
+ }, this._editingCell = { ...e.position }, this._activeCell = this._selection.setActive(n, e.position.col);
2465
+ }
2422
2466
  _toDataIndex(e) {
2423
2467
  return this._sortedIndices[e] ?? e;
2424
2468
  }
2425
2469
  _focusEditor() {
2426
2470
  if (!this._editingCell) return;
2427
- let e = this.shadowRoot?.querySelector(".ft-editor");
2471
+ let e = this.shadowRoot?.querySelector(".ft-editor"), t = this._editing.current;
2472
+ if (e && t?.draft !== void 0) {
2473
+ let n = t.draft;
2474
+ t.draft = void 0, e.value = n, e.focus(), e.type === "text" && e.setSelectionRange(e.value.length, e.value.length);
2475
+ return;
2476
+ }
2428
2477
  if (e && this.shadowRoot.activeElement !== e) {
2429
2478
  if (e.localName === "u-date-picker") {
2430
2479
  let t = e;
@@ -2545,7 +2594,7 @@ var K = {
2545
2594
  return;
2546
2595
  }
2547
2596
  let t = this._toDataIndex(this._activeCell.row), n = this.data[t];
2548
- this._editing.start(this._activeCell, n[e.key]), this._editingCell = { ...this._activeCell }, this._emit("cell-edit-start", {
2597
+ this._editing.start(this._activeCell, n[e.key], n), this._editingCell = { ...this._activeCell }, this._emit("cell-edit-start", {
2549
2598
  row: t,
2550
2599
  col: this._activeCell.col,
2551
2600
  key: e.key,
@@ -2588,12 +2637,20 @@ var K = {
2588
2637
  this._autocompleteState = null;
2589
2638
  let n = this._editing.commit();
2590
2639
  if (this._editingCell = null, !n) return;
2591
- let { row: r, col: i } = n.position, a = this._toDataIndex(r), o = this.visibleColumns[i], s = n.originalValue;
2640
+ let { col: r } = n.position, i = n.row, a = this.data.indexOf(i);
2641
+ if (a < 0) {
2642
+ this._emit("cell-edit-cancel", {
2643
+ row: -1,
2644
+ col: r
2645
+ });
2646
+ return;
2647
+ }
2648
+ let o = this.visibleColumns[r], s = n.originalValue;
2592
2649
  if (o.autocomplete === "strict" && t != null && t !== "" && !this._getAutocompleteCandidates(o, "").includes(String(t))) {
2593
2650
  let n = e("notInList");
2594
- this._markCellInvalid(r, i, () => e("notInList")), this._emit("validation-error", {
2651
+ this._markCellInvalid(i, o, () => e("notInList")), this._emit("validation-error", {
2595
2652
  row: a,
2596
- col: i,
2653
+ col: r,
2597
2654
  key: o.key,
2598
2655
  value: t,
2599
2656
  error: n
@@ -2602,9 +2659,9 @@ var K = {
2602
2659
  }
2603
2660
  if (o.type === "number" && typeof t == "string" || o.type === "date" && typeof t == "string" && p(t) === null || o.type === "datetime" && typeof t == "string" && m(t) === null) {
2604
2661
  let n = o.type === "number" ? "notANumber" : o.type === "date" ? "notADate" : "notADateTime", s = e(n);
2605
- this._markCellInvalid(r, i, () => e(n)), this._emit("validation-error", {
2662
+ this._markCellInvalid(i, o, () => e(n)), this._emit("validation-error", {
2606
2663
  row: a,
2607
- col: i,
2664
+ col: r,
2608
2665
  key: o.key,
2609
2666
  value: t,
2610
2667
  error: s
@@ -2612,11 +2669,11 @@ var K = {
2612
2669
  return;
2613
2670
  }
2614
2671
  if (o.validator) {
2615
- let e = o.validator(t, this.data[a], o);
2672
+ let e = o.validator(t, i, o);
2616
2673
  if (e) {
2617
- this._markCellInvalid(r, i, e), this._emit("validation-error", {
2674
+ this._markCellInvalid(i, o, e), this._emit("validation-error", {
2618
2675
  row: a,
2619
- col: i,
2676
+ col: r,
2620
2677
  key: o.key,
2621
2678
  value: t,
2622
2679
  error: e
@@ -2624,17 +2681,17 @@ var K = {
2624
2681
  return;
2625
2682
  }
2626
2683
  }
2627
- this.data[a][o.key] = t, this._undo.push({
2684
+ i[o.key] = t, this._undo.push({
2628
2685
  label: "cell-edit",
2629
2686
  undo: () => {
2630
- this.data[a][o.key] = s, this.requestUpdate();
2687
+ i[o.key] = s, this.requestUpdate();
2631
2688
  },
2632
2689
  redo: () => {
2633
- this.data[a][o.key] = t, this.requestUpdate();
2690
+ i[o.key] = t, this.requestUpdate();
2634
2691
  }
2635
2692
  }), this.requestUpdate(), this._emit("cell-edit-commit", {
2636
2693
  row: a,
2637
- col: i,
2694
+ col: r,
2638
2695
  key: o.key,
2639
2696
  oldValue: s,
2640
2697
  newValue: t
@@ -2644,7 +2701,7 @@ var K = {
2644
2701
  this._autocompleteState = null;
2645
2702
  let e = this._editing.cancel();
2646
2703
  this._editingCell = null, e && this._emit("cell-edit-cancel", {
2647
- row: e.position.row,
2704
+ row: this.data.indexOf(e.row),
2648
2705
  col: e.position.col
2649
2706
  });
2650
2707
  }
@@ -2929,17 +2986,24 @@ var K = {
2929
2986
  if (t.length === 0) return;
2930
2987
  let n = this._expandRowsForPaste(this._activeCell.row, t.length), r = this._applyPasteData(this._activeCell, t);
2931
2988
  if (this._selectPastedRange(this._activeCell, t), r.length > 0 || n.length > 0) {
2932
- let e = n.length;
2989
+ let e = r.map((e) => this.data[e.row]), t = n.map((e) => this.data[e]);
2933
2990
  this._undo.push({
2934
2991
  label: "paste",
2935
2992
  undo: () => {
2936
- for (let e of r) this.data[e.row][e.key] = e.oldValue;
2937
- e > 0 && this.data.splice(this.data.length - e, e), this.requestUpdate();
2993
+ r.forEach((t, n) => {
2994
+ e[n][t.key] = t.oldValue;
2995
+ });
2996
+ for (let e of t) {
2997
+ let t = this.data.indexOf(e);
2998
+ t >= 0 && this.data.splice(t, 1);
2999
+ }
3000
+ this.requestUpdate();
2938
3001
  },
2939
3002
  redo: () => {
2940
- if (e > 0) for (let t = 0; t < e; t++) this.data.push(this._createEmptyRow());
2941
- for (let e of r) this.data[e.row][e.key] = e.newValue;
2942
- this.requestUpdate();
3003
+ for (let e of t) this.data.push(e);
3004
+ r.forEach((t, n) => {
3005
+ e[n][t.key] = t.newValue;
3006
+ }), this.requestUpdate();
2943
3007
  }
2944
3008
  }), this._dispatchUndoStateEvent();
2945
3009
  }
@@ -3063,7 +3127,7 @@ var K = {
3063
3127
  r.add(i);
3064
3128
  let o = t[e], s = this.data[a][o.key], c = o.type === "boolean" ? !1 : o.type === "number" ? 0 : "";
3065
3129
  this.data[a][o.key] = c, n.push({
3066
- dataRow: a,
3130
+ target: this.data[a],
3067
3131
  key: o.key,
3068
3132
  oldValue: s,
3069
3133
  clearValue: c
@@ -3073,11 +3137,11 @@ var K = {
3073
3137
  n.length > 0 && (this._undo.push({
3074
3138
  label: "clear",
3075
3139
  undo: () => {
3076
- for (let e of n) this.data[e.dataRow][e.key] = e.oldValue;
3140
+ for (let e of n) e.target[e.key] = e.oldValue;
3077
3141
  this.requestUpdate();
3078
3142
  },
3079
3143
  redo: () => {
3080
- for (let e of n) this.data[e.dataRow][e.key] = e.clearValue;
3144
+ for (let e of n) e.target[e.key] = e.clearValue;
3081
3145
  this.requestUpdate();
3082
3146
  }
3083
3147
  }), this._dispatchUndoStateEvent()), this.requestUpdate();
@@ -3089,7 +3153,7 @@ var K = {
3089
3153
  for (let r = e.startCol; r <= e.endCol; r++) {
3090
3154
  let e = t[r], a = this.data[i][e.key], o = e.type === "boolean" ? !1 : e.type === "number" ? 0 : "";
3091
3155
  this.data[i][e.key] = o, n.push({
3092
- dataRow: i,
3156
+ target: this.data[i],
3093
3157
  key: e.key,
3094
3158
  oldValue: a,
3095
3159
  clearValue: o
@@ -3099,11 +3163,11 @@ var K = {
3099
3163
  n.length > 0 && (this._undo.push({
3100
3164
  label: "clear",
3101
3165
  undo: () => {
3102
- for (let e of n) this.data[e.dataRow][e.key] = e.oldValue;
3166
+ for (let e of n) e.target[e.key] = e.oldValue;
3103
3167
  this.requestUpdate();
3104
3168
  },
3105
3169
  redo: () => {
3106
- for (let e of n) this.data[e.dataRow][e.key] = e.clearValue;
3170
+ for (let e of n) e.target[e.key] = e.clearValue;
3107
3171
  this.requestUpdate();
3108
3172
  }
3109
3173
  }), this._dispatchUndoStateEvent()), this.requestUpdate();
@@ -3696,7 +3760,7 @@ var K = {
3696
3760
  `;
3697
3761
  }
3698
3762
  _cellKey(e, t) {
3699
- return `${e}:${t}`;
3763
+ return `${this.getRowId(e)}\u0000${t.key}`;
3700
3764
  }
3701
3765
  _markCellInvalid(e, t, n) {
3702
3766
  let r = this._cellKey(e, t), i = this._invalidCells.get(r);
@@ -3888,38 +3952,40 @@ var K = {
3888
3952
  }, this._fillDrag.targetRange = u, this.requestUpdate();
3889
3953
  }
3890
3954
  _applyFillHandle(e, t) {
3891
- let n = this.visibleColumns, r = [];
3892
- for (let i = t.startCol; i <= t.endCol; i++) {
3893
- let a = n[i], o = [];
3894
- for (let t = e.startRow; t <= e.endRow; t++) o.push(this.data[this._toDataIndex(t)][a.key]);
3895
- let s = this._detectNumericSeries(o);
3955
+ let n = this.visibleColumns, r = [], i = [];
3956
+ for (let a = t.startCol; a <= t.endCol; a++) {
3957
+ let o = n[a], s = [];
3958
+ for (let t = e.startRow; t <= e.endRow; t++) s.push(this.data[this._toDataIndex(t)][o.key]);
3959
+ let c = this._detectNumericSeries(s);
3896
3960
  for (let n = t.startRow; n <= t.endRow; n++) {
3897
3961
  if (n >= e.startRow && n <= e.endRow) continue;
3898
- let t = this._toDataIndex(n), i = this.data[t][a.key], c;
3899
- if (s && n > e.endRow) {
3900
- let t = n - e.endRow, r = s.last + s.diff * t;
3901
- c = a.type === "number" ? r : String(r);
3962
+ let t = this._toDataIndex(n), a = this.data[t][o.key], l;
3963
+ if (c && n > e.endRow) {
3964
+ let t = n - e.endRow, r = c.last + c.diff * t;
3965
+ l = o.type === "number" ? r : String(r);
3902
3966
  } else {
3903
- let t = (n - e.startRow) % o.length;
3904
- c = o[t < 0 ? t + o.length : t];
3967
+ let t = (n - e.startRow) % s.length;
3968
+ l = s[t < 0 ? t + s.length : t];
3905
3969
  }
3906
- this.data[t][a.key] = c, r.push({
3970
+ this.data[t][o.key] = l, r.push({
3907
3971
  dataRow: t,
3908
- key: a.key,
3909
- oldValue: i,
3910
- newValue: c
3911
- });
3972
+ key: o.key,
3973
+ oldValue: a,
3974
+ newValue: l
3975
+ }), i.push(this.data[t]);
3912
3976
  }
3913
3977
  }
3914
3978
  r.length > 0 && (this._undo.push({
3915
3979
  label: "fill-handle",
3916
3980
  undo: () => {
3917
- for (let e of r) this.data[e.dataRow][e.key] = e.oldValue;
3918
- this.requestUpdate();
3981
+ r.forEach((e, t) => {
3982
+ i[t][e.key] = e.oldValue;
3983
+ }), this.requestUpdate();
3919
3984
  },
3920
3985
  redo: () => {
3921
- for (let e of r) this.data[e.dataRow][e.key] = e.newValue;
3922
- this.requestUpdate();
3986
+ r.forEach((e, t) => {
3987
+ i[t][e.key] = e.newValue;
3988
+ }), this.requestUpdate();
3923
3989
  }
3924
3990
  }), this._dispatchUndoStateEvent(), this._emit("fill-handle-apply", {
3925
3991
  sourceRange: e,
@@ -4083,25 +4149,27 @@ var K = {
4083
4149
  }
4084
4150
  _replaceAll() {
4085
4151
  if (!this._findState || this._findState.results.length === 0) return;
4086
- let { results: e, replaceWith: t } = this._findState, n = this.visibleColumns, r = [];
4087
- for (let { row: i, col: a } of e) {
4088
- let e = this._toDataIndex(i), o = n[a].key, s = this.data[e][o];
4089
- this.data[e][o] = t, r.push({
4152
+ let { results: e, replaceWith: t } = this._findState, n = this.visibleColumns, r = [], i = [];
4153
+ for (let { row: a, col: o } of e) {
4154
+ let e = this._toDataIndex(a), s = n[o].key, c = this.data[e][s];
4155
+ this.data[e][s] = t, r.push({
4090
4156
  dataRow: e,
4091
- key: o,
4092
- oldValue: s,
4157
+ key: s,
4158
+ oldValue: c,
4093
4159
  newValue: t
4094
- });
4160
+ }), i.push(this.data[e]);
4095
4161
  }
4096
4162
  r.length > 0 && (this._undo.push({
4097
4163
  label: "replace-all",
4098
4164
  undo: () => {
4099
- for (let e of r) this.data[e.dataRow][e.key] = e.oldValue;
4100
- this.requestUpdate();
4165
+ r.forEach((e, t) => {
4166
+ i[t][e.key] = e.oldValue;
4167
+ }), this.requestUpdate();
4101
4168
  },
4102
4169
  redo: () => {
4103
- for (let e of r) this.data[e.dataRow][e.key] = e.newValue;
4104
- this.requestUpdate();
4170
+ r.forEach((e, t) => {
4171
+ i[t][e.key] = e.newValue;
4172
+ }), this.requestUpdate();
4105
4173
  }
4106
4174
  }), this._dispatchUndoStateEvent(), this._emit("find-replace", {
4107
4175
  type: "replace-all",
@@ -4115,21 +4183,21 @@ var K = {
4115
4183
  }
4116
4184
  _replaceOne() {
4117
4185
  if (!this._findState || this._findState.results.length === 0) return;
4118
- let { currentIndex: e, results: t, replaceWith: n } = this._findState, { row: r, col: i } = t[e], a = this._toDataIndex(r), o = this.visibleColumns[i].key, s = this.data[a][o];
4119
- this.data[a][o] = n, this._undo.push({
4186
+ let { currentIndex: e, results: t, replaceWith: n } = this._findState, { row: r, col: i } = t[e], a = this._toDataIndex(r), o = this.visibleColumns[i].key, s = this.data[a], c = s[o];
4187
+ s[o] = n, this._undo.push({
4120
4188
  label: "replace",
4121
4189
  undo: () => {
4122
- this.data[a][o] = s, this.requestUpdate();
4190
+ s[o] = c, this.requestUpdate();
4123
4191
  },
4124
4192
  redo: () => {
4125
- this.data[a][o] = n, this.requestUpdate();
4193
+ s[o] = n, this.requestUpdate();
4126
4194
  }
4127
4195
  }), this._dispatchUndoStateEvent(), this._emit("find-replace", {
4128
4196
  type: "replace",
4129
4197
  cells: [{
4130
4198
  row: a,
4131
4199
  col: o,
4132
- oldValue: s,
4200
+ oldValue: c,
4133
4201
  newValue: n
4134
4202
  }]
4135
4203
  }), this._findSearch(), this._findGoTo(Math.min(e, this._findState.results.length - 1));
@@ -4363,7 +4431,7 @@ var K = {
4363
4431
  ${this._renderEditor(e, t)}
4364
4432
  </div>
4365
4433
  `;
4366
- let v = s || l, y = this._isCellInvalid(n, r), b = (this._findState?.results ?? []).findIndex((e) => e.row === n && e.col === r), x = b >= 0, S = x && b === this._findState.currentIndex, C = [
4434
+ let v = s || l, y = this._isCellInvalid(e, t), b = (this._findState?.results ?? []).findIndex((e) => e.row === n && e.col === r), x = b >= 0, S = x && b === this._findState.currentIndex, C = [
4367
4435
  "ft-cell",
4368
4436
  `ft-type-${t.type ?? "text"}`,
4369
4437
  `ft-align-${W(t)}`,
@@ -4376,9 +4444,9 @@ var K = {
4376
4444
  i?.continues ? "ft-merge-continued" : "",
4377
4445
  i?.opensDown ? "ft-merge-open" : "",
4378
4446
  t.reveal === "hover" ? "ft-reveal-hover" : ""
4379
- ].filter(Boolean).join(" "), w = this._toDataIndex(n), T = this._comments.get(w)?.get(t.key) ?? null, E = T !== null, O = E ? a`<div class="ft-comment-indicator" title=${T}></div>` : o;
4447
+ ].filter(Boolean).join(" "), w = this._comments.size > 0 ? this._comments.get(this.getRowId(e))?.get(t.key) ?? null : null, T = w !== null, E = T ? a`<div class="ft-comment-indicator" title=${w}></div>` : o;
4380
4448
  return a`
4381
- <div class=${E ? C + " ft-has-comment" : C}
4449
+ <div class=${T ? C + " ft-has-comment" : C}
4382
4450
  role="gridcell"
4383
4451
  tabindex="-1"
4384
4452
  aria-colindex=${r + 1}
@@ -4392,7 +4460,7 @@ var K = {
4392
4460
  @mouseenter=${() => this._onCellMouseEnter(n, r)}
4393
4461
  @click=${(e) => this._onCellClickEvent(e, n, r)}
4394
4462
  @dblclick=${() => this._onCellDblClick(n, r)}>
4395
- ${O}
4463
+ ${E}
4396
4464
  ${D(e[t.key], e, t)}
4397
4465
  </div>
4398
4466
  `;
@@ -4404,7 +4472,7 @@ var K = {
4404
4472
  <input class="ft-editor ft-editor-number" type="text" inputmode="decimal" aria-label=${t.label}
4405
4473
  .value=${typeof n == "number" ? M(n) : r}
4406
4474
  @keydown=${this._onEditorKeyDown}
4407
- @blur=${() => this._commitEdit()}>
4475
+ @blur=${this._onEditorBlur}>
4408
4476
  `;
4409
4477
  if (t.type === "date" || t.type === "datetime") {
4410
4478
  let e = t.type === "datetime";
@@ -4414,7 +4482,7 @@ var K = {
4414
4482
  .value=${Ze(n, e)}
4415
4483
  @keydown=${this._onEditorKeyDown}
4416
4484
  @change=${() => this._commitEdit()}
4417
- @blur=${() => this._commitEdit()}></u-date-picker>
4485
+ @blur=${this._onEditorBlur}></u-date-picker>
4418
4486
  `;
4419
4487
  }
4420
4488
  if (t.type === "select" && t.options && t.options.length > 0) {
@@ -4422,7 +4490,7 @@ var K = {
4422
4490
  return a`
4423
4491
  <select class="ft-editor" aria-label=${t.label}
4424
4492
  @keydown=${this._onEditorKeyDown}
4425
- @blur=${() => this._commitEdit()}
4493
+ @blur=${this._onEditorBlur}
4426
4494
  @change=${() => this._commitEdit()}>
4427
4495
  ${r ? e.map((e) => a`<option value=${e} ?selected=${e === n}>${e}</option>`) : e.map((e) => a`<option value=${String(e.value)} ?selected=${e.value === n}>${e.label}</option>`)}
4428
4496
  </select>
@@ -4439,7 +4507,7 @@ var K = {
4439
4507
  .value=${r}
4440
4508
  @input=${(e) => this._onAutocompleteInput(e, t)}
4441
4509
  @keydown=${this._onEditorKeyDown}
4442
- @blur=${() => this._commitEdit()}>
4510
+ @blur=${this._onEditorBlur}>
4443
4511
  ${e.length > 0 ? a`
4444
4512
  <div class="ft-autocomplete-dropdown" id="ft-autocomplete-list" role="listbox" aria-label=${t.label}>
4445
4513
  ${e.map((e, t) => a`
@@ -4459,7 +4527,7 @@ var K = {
4459
4527
  <input class="ft-editor" type="text" aria-label=${t.label}
4460
4528
  .value=${r}
4461
4529
  @keydown=${this._onEditorKeyDown}
4462
- @blur=${() => this._commitEdit()}>
4530
+ @blur=${this._onEditorBlur}>
4463
4531
  `;
4464
4532
  }
4465
4533
  };
@@ -4534,4 +4602,4 @@ function $(e) {
4534
4602
  }
4535
4603
  }
4536
4604
  //#endregion
4537
- export { k as a, A as i, W as n, D as o, z as r, Q as t };
4605
+ export { O as a, A as i, W as n, D as o, z as r, Q as t };
@@ -175,6 +175,7 @@ export declare class FlexTable extends LitElement {
175
175
  private _columnWidths;
176
176
  private _hostResizeObserver;
177
177
  /** Keyed by dataIndex → colKey → comment text */
178
+ /** Cell comments by row id (`getRowId`) and column key — they stay on their rows when rows move. */
178
179
  private _comments;
179
180
  private _isDragOver;
180
181
  get visibleColumns(): ColumnDefinition[];
@@ -191,15 +192,13 @@ export declare class FlexTable extends LitElement {
191
192
  /** Number of rows after filtering (before pagination). */
192
193
  get filteredRowCount(): number;
193
194
  /**
194
- * The row selection. `selectedIds` is every selected row — on every page, including rows not in
195
- * `data` now. `selectedRows` and `selectedIndices` are the selected rows that `data` holds (in
196
- * `data` order, filtered-out rows included) and their positions in it.
195
+ * The selected rows that `data` holds, in `data` order (filtered-out rows included) — the same shape
196
+ * as `u-rich-table`'s. Every selected id, on every page, is `selectedRowIds`; positions in `data` come
197
+ * with `selection-change` (`selectedIndices`).
197
198
  */
198
- getSelectedRows(): {
199
- selectedIds: string[];
200
- selectedIndices: number[];
201
- selectedRows: DataRow[];
202
- };
199
+ getSelectedRows(): DataRow[];
200
+ /** The selection as `selection-change` reports it. */
201
+ private _selectionSnapshot;
203
202
  /** The id row selection keeps for `row` — its `rowKey` value as a string, or `#n` for a row without one. */
204
203
  getRowId(row: DataRow): string;
205
204
  /** Ids of the selected rows — every page, including rows not in `data` now. A copy. */
@@ -228,6 +227,8 @@ export declare class FlexTable extends LitElement {
228
227
  private _dispatchRowSelectionEvent;
229
228
  /** Ids of the rows in view, top to bottom (after filter and sort). Cached per view and `rowKey`. */
230
229
  private _visibleRowIds;
230
+ /** Drops comments on `#n` rows `data` no longer holds — such a row cannot come back. */
231
+ private _pruneGoneUnkeyedComments;
231
232
  /** Drops selected `#n` ids whose rows `data` no longer holds. Returns whether anything was dropped. */
232
233
  private _pruneGoneUnkeyedRows;
233
234
  /**
@@ -341,10 +342,11 @@ export declare class FlexTable extends LitElement {
341
342
  */
342
343
  getComment(dataIndex: number, colKey: string): string | null;
343
344
  /**
344
- * Get all comments as a flat array.
345
+ * Every comment on a row `data` holds, in `data` order — with the row's current index and its id.
345
346
  */
346
347
  getAllComments(): Array<{
347
348
  dataIndex: number;
349
+ id: string;
348
350
  colKey: string;
349
351
  text: string;
350
352
  }>;
@@ -406,8 +408,20 @@ export declare class FlexTable extends LitElement {
406
408
  protected updated(changedProps: PropertyValues): void;
407
409
  /** Recompute filter → sort pipeline. */
408
410
  private _recomputeView;
411
+ /**
412
+ * Keeps an open editor on the row it was started on after the view changed under it (a refresh put
413
+ * rows above it, a sort moved it). A row that left `data`, or that a filter hid, ends the edit.
414
+ */
415
+ private _followEditingRow;
409
416
  /** Map visual row index to data row index */
410
417
  private _toDataIndex;
418
+ /** Set while the edited row's editor is drawn again in the row's new place (cleared after that update). */
419
+ private _editorMoving;
420
+ /**
421
+ * Blur commits the edit — except the blur of the editor the update is replacing because its row
422
+ * moved (Chromium fires it while removing the element) or of one already gone.
423
+ */
424
+ private _onEditorBlur;
411
425
  private _focusEditor;
412
426
  private _measureViewport;
413
427
  private _warnedUnconstrained;
@@ -601,6 +615,7 @@ export declare class FlexTable extends LitElement {
601
615
  private _renderNumberFilter;
602
616
  /** 셀 오류 표시 — 우리 문장은 «그릴 때 찾는» 함수로(로캘 전환에 따라오게), 소비자 `validator` 문장은 그대로. */
603
617
  private _invalidCells;
618
+ /** A cell named by its row's id and its column's key — so the mark stays on the cell when the view reorders. */
604
619
  private _cellKey;
605
620
  private _markCellInvalid;
606
621
  private _isCellInvalid;
@@ -1,3 +1,3 @@
1
- import { a as e, i as t, n, o as r, r as i, t as a } from "./flex-table-M_oC8OXD.js";
1
+ import { a as e, i as t, n, o as r, r as i, t as a } from "./flex-table-B1W8VEBa.js";
2
2
  import { t as o } from "./locale-Cfm-vJji.js";
3
3
  export { a as FlexTable, e as RowSelectionState, t as UndoStack, n as effectiveAlign, i as exportData, o as flexTableLocale, r as renderCell };
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as e } from "./flex-table-M_oC8OXD.js";
1
+ import { t as e } from "./flex-table-B1W8VEBa.js";
2
2
  import { i as t } from "./query-Dw4iO0ir.js";
3
3
  import { t as n } from "./source--q5s2Oti.js";
4
4
  import { t as r } from "./view-DiLr-TdY.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iyulab/flex-table",
3
- "version": "0.56.0",
3
+ "version": "0.57.0",
4
4
  "description": "A minimalist, input-centric data grid web component",
5
5
  "type": "module",
6
6
  "main": "./dist/flex-table.js",
@@ -119,7 +119,7 @@ Enter/Escape and blur-to-commit behavior.
119
119
  | `selectAll()` / `deselectAll()` | `selectAll`: the rows in view (multi mode only) · `deselectAll`: every page |
120
120
  | `selectWhere(predicate)` | `(row, dataIndex) => boolean` over the visible rows |
121
121
  | `setSelection(ids)` | Replace with these row ids; the same set again fires nothing |
122
- | `getSelectedRows()` | `{ selectedIds, selectedIndices, selectedRows }` — ids: all pages · rows: the ones `data` holds |
122
+ | `getSelectedRows()` | `DataRow[]` — the selected rows `data` holds (as `u-rich-table`) · ids of every page: `selectedRowIds` |
123
123
  | `getRowId(row)` · `selectedRowIds` | A row's id · the selected ids (all pages) |
124
124
 
125
125
  ### Filtering, undo, comments, import/export
@@ -146,7 +146,7 @@ cast, and the React `on*` props carry the same types.
146
146
  | `cell-select` | `{ row, col }` (or `null`) |
147
147
  | `cell-edit-start` | `{ row, col, key, value }` |
148
148
  | `cell-edit-commit` | `{ row, col, key, oldValue, newValue }` |
149
- | `cell-edit-cancel` | `{ row, col }` |
149
+ | `cell-edit-cancel` | `{ row, col }` — `row`: data index (`-1` once the row left `data`) |
150
150
  | `validation-error` | `{ row, col, key, value, error }` |
151
151
  | `row-add` | `{ row, index }` |
152
152
  | `row-delete` | `{ indices, rows }` |
@@ -168,7 +168,7 @@ cast, and the React `on*` props carry the same types.
168
168
  | `clipboard-error` | `{ action: 'copy' \| 'paste', error }` — copy could not put the text on the clipboard (no `clipboard-copy`/`clipboard-cut` follows; a cut clears nothing), or paste could not read it |
169
169
  | `fill-handle-apply` | `{ sourceRange, targetRange, cells }` |
170
170
  | `find-replace` | `{ type: 'replace' \| 'replace-all', cells }` |
171
- | `comment-change` | `{ dataIndex, colKey, text }` |
171
+ | `comment-change` | `{ dataIndex, id, colKey, text }` |
172
172
  | `data-import` | `{ count }` |
173
173
  | `undo-state-change` | `{ canUndo, canRedo }` |
174
174
  | `context-menu` | `{ x, y, row, col, key, value, rowData }` — cancelable; `preventDefault()` suppresses the built-in menu |