@iyulab/flex-table 0.55.2 → 0.56.1

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,63 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.56.1] - 2026-10-07
4
+
5
+ ### Fixed
6
+
7
+ - **An edit is written to the row it was started on.** The open editor remembered a screen position and the commit
8
+ wrote whatever row stood there, so a refresh that put a row above the one being edited (or a sort while editing)
9
+ sent the typed value into another row. The editor now follows its row — drawn again in the row's new place with
10
+ what was typed and the focus — and an edit whose row left `data` (or a filter hid) is cancelled.
11
+ - **Undo and redo write the rows they changed** (cell edits, `updateRows`, paste, clear, fill, replace). They wrote
12
+ `data` positions, so after `data` changed in between they changed other rows.
13
+ - **Comments stay on their rows.** They were kept by `data` index, so inserting or deleting a row moved every comment
14
+ below it onto the neighbouring row. A deleted row's comments leave with it (and come back on undo).
15
+ - **Validation marks stay on their cells** when the view reorders while one is shown.
16
+ - `cell-edit-cancel` reports `row` as the data index, like the other edit events (it was the screen position) —
17
+ `-1` when the row is no longer in `data`.
18
+
19
+ ### Added
20
+
21
+ - `comment-change` and `getAllComments()` carry the row's `id`.
22
+
23
+ ## [0.56.0] - 2026-10-07
24
+
25
+ ### Fixed
26
+
27
+ - **Row selection stays on its rows.** The checkmarks were kept by screen position, so sorting, filtering, inserting
28
+ or deleting rows moved them onto other rows without a `selection-change` — select a row, sort, and a different row
29
+ was selected (and a bulk action took it). Replacing `data` with other rows of the same length carried the
30
+ checkmarks onto the new rows. Selection is now kept by row id (see `rowKey` below).
31
+ - **`deleteRows()` without indices deletes the checked rows in a `selectable` grid.** It deleted the rows of the
32
+ cell selection (the active cell's row), so a «Delete selected» button removed a row nobody checked. With nothing
33
+ checked it now deletes nothing. A grid without checkboxes keeps deleting the cell selection's rows.
34
+
35
+ ### Added
36
+
37
+ - **`rowKey`** (`row-key`, default `'_id'` — the field `u-rich-table` reads) — the field, or a function of the row,
38
+ that names a row. A keyed row stays selected when `data` is replaced, so a selection spans server pages. A row
39
+ without a key is named by the row object (a session-local id `#n`) and leaves the selection when `data` no longer
40
+ holds it.
41
+ - `getRowId(row)`, the `selectedRowIds` getter and `setSelection(ids)` (does nothing, and fires nothing, when the
42
+ selection already is `ids`).
43
+ - `selection-change` carries `selectedIds` — every selected row, on every page — next to `selectedRows` and
44
+ `selectedIndices` (the selected rows `data` holds). `row-activate` carries the row's `id`.
45
+
46
+ ### Changed (breaking)
47
+
48
+ - The header checkbox and `selectAll()` act on the rows in view: rows selected on another page stay selected, and
49
+ unchecking the header deselects only the rows in view. `deselectAll()` still clears every page. A selected row a
50
+ filter hides stays selected (and in `selectedRows`).
51
+ - `RowSelectionState` (exported) keeps ids: `isSelected(id)`, `toggle(id)`, `select(id)`, `selectAll(ids)`,
52
+ `deselectMany(ids)`, `set(ids)`, `retain(keep)`, `selectRange(orderedIds, from, to)`, `isAllSelected(ids)`,
53
+ `isSomeSelected(ids)`, `selectedIds`. `setRowCount` and `selectedIndices` are gone.
54
+ - `clear-selection-on-data-change` keeps its meaning; it is no longer needed to stop a replacement from moving the
55
+ checkmarks.
56
+
57
+ **Migrating**: give rows a stable key (`_id`, or set `rowKey`) when selection should survive a reload or a page change.
58
+ Read `selectedIds` for «what is selected» across pages, `selectedRows` for the rows on hand. A «Delete selected» button
59
+ on a `selectable` grid can keep calling `deleteRows()`.
60
+
3
61
  ## [0.55.2] - 2026-10-07
4
62
 
5
63
  ### Documentation
package/README.md CHANGED
@@ -108,6 +108,7 @@ guarantee about a *constrained* host. `height-model.browser.test.ts` pins both s
108
108
  | `maxRows` | `max-rows` | `number` | `0` | Max row count (0 = unlimited); blocks `addRow()` and paste expansion |
109
109
  | `maxUndoSize` | `max-undo-size` | `number` | `100` | Max undo history stack size |
110
110
  | `selectable` | `selectable` | `boolean` | `false` | Enable row-level checkbox selection |
111
+ | `rowKey` | `row-key` | `string \| (row) => unknown` | `'_id'` | What names a row — selection is kept by this id (see [Row Selection](#row-selection)) |
111
112
  | `selectionMode` | `selection-mode` | `'single' \| 'multi'` | `'multi'` | Row selection mode |
112
113
  | `dataMode` | `data-mode` | `'client' \| 'server'` | `'client'` | Client-side or server-side data processing |
113
114
  | `footerData` | `footer-data` | `Record<string, string \| TemplateResult> \| null` | `null` | Footer/summary row data (keys match column keys) |
@@ -235,7 +236,7 @@ Every row keeps its own value. Sorting, filtering, copying, CSV/XLSX export and
235
236
  | Method | Returns | Description |
236
237
  |--------|---------|-------------|
237
238
  | `addRow(row?, index?)` | `DataRow \| null` | Add a row. Returns `null` if `maxRows` reached |
238
- | `deleteRows(indices?)` | `void` | Delete rows by data index (default: selected rows) |
239
+ | `deleteRows(indices?)` | `void` | Delete rows by data index. Without indices: the checked rows when `selectable` (none checked — nothing), otherwise the rows of the cell selection |
239
240
  | `updateRows(changes)` | `void` | Batch update cells as single undo action. `changes: Array<{ row, key, value }>` |
240
241
  | `refreshData()` | `void` | Force re-render after in-place data mutation |
241
242
 
@@ -253,11 +254,21 @@ Every row keeps its own value. Sorting, filtering, copying, CSV/XLSX export and
253
254
 
254
255
  | Method | Returns | Description |
255
256
  |--------|---------|-------------|
256
- | `selectAll()` | `void` | Select all visible rows (multi mode only) |
257
- | `deselectAll()` | `void` | Deselect all rows |
258
- | `getSelectedRows()` | `{ selectedIndices, selectedRows }` | Get selected row data |
257
+ | `selectAll()` | `void` | Select every row in view, after filtering (multi mode only). Rows on other pages stay as they are |
258
+ | `deselectAll()` | `void` | Deselect every row, on every page |
259
+ | `selectWhere(predicate)` | `void` | Add the rows in view for which `predicate(row, dataIndex)` is true |
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 |
262
+ | `getRowId(row)` | `string` | The id selection keeps for a row |
263
+ | `selectedRowIds` (getter) | `ReadonlySet<string>` | Ids of the selected rows, all pages (a copy) |
259
264
 
260
- Row selection is index-based (there is no row-key concept), so replacing `data` with a same-length but different set of rows leaves the selection pointing at the new rows occupying the old indices. If selection drives a bulk action (status changes, bulk delete, etc.), set `clear-selection-on-data-change` so a `data` swap always resets selection and re-fires `selection-change` with an empty selection:
265
+ Row selection is kept by **row id**, not by position, so a checkmark stays on its row through sorting, filtering, inserts and deletes. The id is the row's `row-key` field — `_id` by default, the same field `u-rich-table` reads — or the result of a `rowKey` function:
266
+
267
+ ```ts
268
+ table.rowKey = 'orderNo'; // or: table.rowKey = (row) => `${row.site}/${row.no}`
269
+ ```
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):
261
272
 
262
273
  ```html
263
274
  <flex-table selectable clear-selection-on-data-change></flex-table>
@@ -295,7 +306,7 @@ type-checks without a cast. The React wrapper's `on*` props carry the same types
295
306
  | `cell-select` | `{ row, col }` or `null` | Cell focus changed (`null` when no cell is active) |
296
307
  | `cell-edit-start` | `{ row, col, key, value }` | Cell editing started |
297
308
  | `cell-edit-commit` | `{ row, col, key, oldValue, newValue }` | Cell value committed |
298
- | `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) |
299
310
  | `validation-error` | `{ row, col, key, value, error }` | Cell validator rejected value, or a `number` cell got text that is not a number |
300
311
 
301
312
  ### Data Events
@@ -304,13 +315,13 @@ type-checks without a cast. The React wrapper's `on*` props carry the same types
304
315
  |-------|--------|-------------|
305
316
  | `row-add` | `{ row, index }` | Row added |
306
317
  | `row-delete` | `{ indices, rows }` | Rows deleted |
307
- | `row-activate` | `{ row, index, col, key }` | Enter pressed on a non-editable cell — the grid's own contract for "activate this row" (e.g. navigate to a detail view), guaranteed even though the internal Enter handler prevents the keystroke from reliably reaching a listener the host attaches to the same element |
318
+ | `row-activate` | `{ row, id, index, col, key }` | Enter pressed on a non-editable cell — the grid's own contract for "activate this row" (e.g. navigate to a detail view), guaranteed even though the internal Enter handler prevents the keystroke from reliably reaching a listener the host attaches to the same element |
308
319
  | `batch-update` | `{ changes: [{ row, key, oldValue, newValue }] }` | Batch update applied |
309
320
  | `row-reorder` | `{ from, to }` | Row dragged to a new place (data indices) |
310
321
  | `data-import` | `{ count }` | Rows imported from a file |
311
322
  | `fill-handle-apply` | `{ sourceRange, targetRange, cells }` | Fill handle wrote `cells` (`{ dataRow, key, oldValue, newValue }`) |
312
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 |
313
- | `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 |
314
325
 
315
326
  ### Column Events
316
327
 
@@ -336,7 +347,7 @@ type-checks without a cast. The React wrapper's `on*` props carry the same types
336
347
 
337
348
  | Event | Detail | Description |
338
349
  |-------|--------|-------------|
339
- | `selection-change` | `{ selectedIndices, selectedRows }` | Row checkbox selection changed |
350
+ | `selection-change` | `{ selectedIds, selectedIndices, selectedRows }` | Row checkbox selection changed — `selectedIds` covers every page, `selectedRows` the rows `data` holds |
340
351
 
341
352
  ### Clipboard Events
342
353
 
@@ -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;
@@ -1,30 +1,41 @@
1
1
  import type { SelectionMode } from '../models/types.js';
2
2
  /**
3
- * Manages row-level selection state (checkbox-based).
4
- * Separate from cell-level SelectionState.
3
+ * Row-level (checkbox) selection, kept as a set of row ids — see `FlexTable.getRowId`.
4
+ * Separate from the cell-level `SelectionState`.
5
+ *
6
+ * An id names a row, not a position: sorting, filtering, inserting or deleting rows leaves the checkmarks on the
7
+ * rows they were put on. The header checkbox and "select all" act on the rows currently in view (`ids` arguments);
8
+ * ids of rows that are not in view (another server page) stay selected.
5
9
  */
6
10
  export declare class RowSelectionState {
7
11
  private _selected;
8
12
  private _mode;
9
- private _rowCount;
10
13
  get mode(): SelectionMode;
11
14
  set mode(value: SelectionMode);
12
- setRowCount(count: number): void;
13
- get selectedIndices(): number[];
15
+ /** Selected ids, in the order they were selected. */
16
+ get selectedIds(): string[];
14
17
  get selectedCount(): number;
15
- get isAllSelected(): boolean;
16
- get isSomeSelected(): boolean;
17
- isSelected(index: number): boolean;
18
- toggle(index: number): void;
19
- select(index: number): void;
20
- deselect(index: number): void;
21
- selectAll(): void;
18
+ isSelected(id: string): boolean;
19
+ /** Every one of `ids` (the rows in view) is selected — false when `ids` is empty. */
20
+ isAllSelected(ids: readonly string[]): boolean;
21
+ /** Some but not all of `ids` (the rows in view) are selected. */
22
+ isSomeSelected(ids: readonly string[]): boolean;
23
+ toggle(id: string): void;
24
+ select(id: string): void;
25
+ deselect(id: string): void;
26
+ /** Adds `ids` to the selection (multi mode only — one slot cannot hold a set). */
27
+ selectAll(ids: readonly string[]): void;
28
+ /** Removes `ids` from the selection — the rest stays. */
29
+ deselectMany(ids: readonly string[]): void;
22
30
  deselectAll(): void;
31
+ /** Replaces the selection with `ids` (single mode keeps the last). Returns whether it changed. */
32
+ set(ids: Iterable<string>): boolean;
33
+ /** Keeps only the ids `keep` accepts. Returns whether anything was dropped. */
34
+ retain(keep: (id: string) => boolean): boolean;
23
35
  /**
24
- * Selects every index between from and to, inclusive, in either direction.
25
- * Used for shift-click range selection anchored at the last toggled row.
26
- * Single mode: only the endpoint (to) ends up selected, matching toggle()'s
27
- * "one at a time" semantics — a range doesn't make sense with one slot.
36
+ * Selects `ids` between the positions of `from` and `to` in `ordered` (the rows in view, top to bottom), inclusive,
37
+ * in either direction — shift-click range selection anchored at the last toggled row. Single mode selects only
38
+ * `to`, matching `toggle()`'s one-slot semantics. An anchor that is no longer in view selects only `to`.
28
39
  */
29
- selectRange(from: number, to: number): void;
40
+ selectRange(ordered: readonly string[], from: string, to: string): void;
30
41
  }
package/dist/events.d.ts CHANGED
@@ -19,7 +19,9 @@ export interface CellChange {
19
19
  */
20
20
  export interface FlexTableEventMap {
21
21
  /** Row selection changed (`selectable`). Indices are data indices. */
22
+ /** `selectedIds` — every selected row (all pages, see `rowKey`); `selectedRows`/`selectedIndices` — the selected rows `data` holds. */
22
23
  'selection-change': CustomEvent<{
24
+ selectedIds: string[];
23
25
  selectedIndices: number[];
24
26
  selectedRows: DataRow[];
25
27
  }>;
@@ -83,8 +85,10 @@ export interface FlexTableEventMap {
83
85
  to: number;
84
86
  }>;
85
87
  /** Enter on a non-editable cell — "activate this row". `index` is the data index, `col` the visible column index. */
88
+ /** `id` — the row's id (`getRowId`); `index` — its position in `data`; `key` — the active column's key. */
86
89
  'row-activate': CustomEvent<{
87
90
  row: DataRow;
91
+ id: string;
88
92
  index: number;
89
93
  col: number;
90
94
  key: string | undefined;
@@ -122,8 +126,10 @@ export interface FlexTableEventMap {
122
126
  value: unknown;
123
127
  error: string;
124
128
  }>;
129
+ /** `id` — the row's id (comments stay on their rows); `dataIndex` — its position in `data` now (-1 when not loaded). */
125
130
  'comment-change': CustomEvent<{
126
131
  dataIndex: number;
132
+ id: string;
127
133
  colKey: string;
128
134
  text: string | null;
129
135
  }>;