@guiexpert/table 1.1.107 → 1.1.109

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.
@@ -9,6 +9,7 @@ import { CellRendererIf } from '../../../renderer/cell-render.if.js';
9
9
  import { DerivedValuesChangeIf } from './derived-values-change.if.js';
10
10
  import { ShowValuesAsIf } from '../../../calc/show-values-as.type.js';
11
11
  import { RowDropTargetIf } from '../../../row-drag/row-drop-target.if.js';
12
+ import { AppendRowsContextIf, AppendRowsModelResultIf } from '../../../live-append/append-rows-options.if.js';
12
13
 
13
14
  /**
14
15
  * @class AreaModelObjectArray
@@ -175,6 +176,11 @@ export declare class AreaModelObjectArray<T> extends AbstractAreaModel<T> implem
175
176
  * @internal
176
177
  */
177
178
  protected sorterService: SorterService;
179
+ /**
180
+ * True while the displayed rows are in the order of {@link getAllRows} (unsorted, maybe filtered) – then the rows a
181
+ * ring buffer removes at the start are a prefix of the displayed rows (no O(n) search, see {@link appendRows}).
182
+ */
183
+ private displayInDataOrder;
178
184
  /** Cached „Show values as“ values per property (cleared on filter, sorting and invalidation). */
179
185
  private showValuesCache;
180
186
  /**
@@ -251,6 +257,43 @@ export declare class AreaModelObjectArray<T> extends AbstractAreaModel<T> implem
251
257
  * bodyAreaModel.setRows(newRows);
252
258
  */
253
259
  setRows(rows: T[]): void;
260
+ /**
261
+ * Live data (since 1.1.108): appends rows **in place** to {@link getAllRows} (your array – no copy) and to the
262
+ * displayed rows: the active filter runs only for the new rows (O(k)), with an active sort order they are sorted
263
+ * and merged in (O(n + k), stable – behind equal rows, like a sort of all rows). With `ctx.maxRows` the oldest
264
+ * rows are removed from the start (ring buffer) – in blocks of at least 1 % of `maxRows` (moving the rows of an
265
+ * array is O(n)), so the row count stays between 99 % and 100 % of `maxRows`. Use `tableApi.appendRows()` – it passes filter and order and moves
266
+ * selection, focus and scroll position along.
267
+ *
268
+ * @param {unknown[]} newRows - the new rows (appended in this order)
269
+ * @param {AppendRowsContextIf<T>} [ctx] - active filter, sort order, ring buffer size
270
+ * @returns {AppendRowsModelResultIf} the removed and inserted display positions
271
+ *
272
+ * @example
273
+ * const result = bodyAreaModel.appendRows(lines, { maxRows: 100_000 });
274
+ * console.log(result.insertedPositions); // where the new rows are shown
275
+ *
276
+ * @since 1.1.108
277
+ */
278
+ appendRows(newRows: unknown[], ctx?: AppendRowsContextIf<T>): AppendRowsModelResultIf;
279
+ /** Removes the first `count` rows of the data (in place) and of the displayed rows; their old display positions. */
280
+ private removeFirstRows;
281
+ private appendShown;
282
+ /** Sorts the new rows and merges them into the (sorted) displayed rows from the back – stable, in place. */
283
+ private mergeSorted;
284
+ /**
285
+ * The comparator of sort items (first item = primary key, the following ones break ties), as used by
286
+ * {@link doSort} – e.g. to sort new rows in (live data). `undefined` if no item sorts (state "").
287
+ *
288
+ * @param {SortItem[]} sortItems - column index and sort state per sorted column
289
+ * @returns {((a: T, b: T) => number) | undefined} the comparator
290
+ *
291
+ * @example
292
+ * const compare = bodyAreaModel.createSortComparator([new SortItem(2, "desc")]);
293
+ *
294
+ * @since 1.1.108
295
+ */
296
+ createSortComparator(sortItems: SortItem[]): ((a: T, b: T) => number) | undefined;
254
297
  /**
255
298
  * Filters both the original rows and filtered rows arrays using the provided predicate function.
256
299
  * Unlike `externalFilterChanged`, this method permanently modifies the original rows array.
@@ -1,4 +1,5 @@
1
1
  import { RowDropTargetIf } from '../../../row-drag/row-drop-target.if.js';
2
+ import { AppendRowsContextIf, AppendRowsModelResultIf } from '../../../live-append/append-rows-options.if.js';
2
3
  import { DerivedValuesChangeIf } from './derived-values-change.if.js';
3
4
  import { CellRendererIf } from '../../../renderer/cell-render.if.js';
4
5
  import { CheckedType } from '../../common/checked-type.js';
@@ -316,6 +317,19 @@ export interface AreaModelIf {
316
317
  * @since 1.1.95
317
318
  */
318
319
  moveRows?(rowIndices: number[], target: RowDropTargetIf): boolean;
320
+ /**
321
+ * Optional (live data, since 1.1.108): appends rows **in place** – the active filter only for the new rows, sorted
322
+ * in by the active order, oldest rows removed for a ring buffer – and reports the display positions, so the table
323
+ * moves selection, focus and scroll position along (`tableApi.appendRows()`). Implemented by
324
+ * {@link AreaModelObjectArray}; `false` = not supported (the table logs a warning and changes nothing).
325
+ *
326
+ * @param {unknown[]} rows - the new rows
327
+ * @param {AppendRowsContextIf} ctx - active filter, sort order, ring buffer size
328
+ * @returns {AppendRowsModelResultIf | false} what changed, or false
329
+ * @example
330
+ * const result = bodyModel.appendRows?.(lines, { maxRows: 100_000 });
331
+ */
332
+ appendRows?(rows: unknown[], ctx: AppendRowsContextIf): AppendRowsModelResultIf | false;
319
333
  /**
320
334
  * Optional: drops cached, derived values (e.g. the totals of an {@link AreaModelAggregateFooter}). The table calls
321
335
  * it for all three areas when data may have changed: filter, sorting, editing, `updateCells()` and the public
@@ -15,6 +15,18 @@ export declare class ColAndRowspanModel {
15
15
  /** Row count of the area model at the last {@link init} (−1: not initialized yet). */
16
16
  protected rowCountAtInit: number;
17
17
  init(): void;
18
+ /** Adds the spans starting in a row. */
19
+ private scanRow;
20
+ /**
21
+ * Live data (since 1.1.108): rows were inserted (and maybe removed at the start) – scans only the inserted rows
22
+ * (O(k)) instead of all rows ({@link init}: O(n) per update, because most area models report a possible span of 31).
23
+ * Only possible while no span exists; otherwise (or before the first init) false – then {@link initIfRowCountChanged}
24
+ * builds the ranges again.
25
+ *
26
+ * @param {ArrayLike<number>} insertedPositions - display indices of the inserted rows (after the step), ascending
27
+ * @returns {boolean} true if the ranges are up to date
28
+ */
29
+ updateForInsertedRows(insertedPositions: ArrayLike<number>): boolean;
18
30
  /**
19
31
  * Builds the ranges again if the row count of the area model changed since the last {@link init} – e.g. after
20
32
  * `setRows()` or a filter (the old ranges could point to rows that no longer exist). Cheap otherwise.
@@ -1,6 +1,7 @@
1
1
  import { FocusType } from './focus.type.js';
2
2
  import { FocusModelIf } from './focus-model.if.js';
3
3
  import { EventFocusChangedListenerIf } from './event-focus-changed-listener.if.js';
4
+ import { RowIndexShift } from '../live-append/row-index-shift.js';
4
5
 
5
6
  /**
6
7
  * The `FocusModel` class is an implementation of the `FocusModelIf` interface.
@@ -63,6 +64,17 @@ export declare class FocusModel implements FocusModelIf {
63
64
  hasChanged(): boolean;
64
65
  clear(): void;
65
66
  setFocus(rowIndex: number, columnIndex: number): void;
67
+ /**
68
+ * Live data (since 1.1.108): the focused row moves along when rows were removed at the start or inserted
69
+ * (`tableApi.appendRows()`). The same row keeps the focus without an event; if it was removed, the next remaining
70
+ * row gets it (with an event – the table keeps it inside the rows).
71
+ *
72
+ * @param {RowIndexShift} shift - how the display indices move
73
+ *
74
+ * @example
75
+ * focusModel.adjustForRowChanges?.(new RowIndexShift([0, 1], [])); // row 5 → row 3
76
+ */
77
+ adjustForRowChanges(shift: RowIndexShift): void;
66
78
  hasFocus(rowIndex: number, columnIndex: number): boolean;
67
79
  getFocus(): [number, number];
68
80
  }
@@ -1,4 +1,5 @@
1
1
  import { EventFocusChangedListenerIf } from './event-focus-changed-listener.if.js';
2
+ import { RowIndexShift } from '../live-append/row-index-shift.js';
2
3
 
3
4
  /**
4
5
  * The focused cell of a table (moved by click and arrow keys; F2 edits it). The implementation is {@link FocusModel};
@@ -31,4 +32,14 @@ export interface FocusModelIf {
31
32
  hasChanged(): boolean;
32
33
  /** Resets {@link hasChanged}. */
33
34
  clearChanged(): void;
35
+ /**
36
+ * Optional (live data, since 1.1.108): the focused row moves along when rows were removed at the start or
37
+ * inserted (`tableApi.appendRows()`). Same row: no event. Focused row removed: the next remaining row gets the
38
+ * focus (with an event).
39
+ *
40
+ * @param {RowIndexShift} shift - how the display indices move
41
+ * @example
42
+ * focusModel.adjustForRowChanges?.(new RowIndexShift([0], []));
43
+ */
44
+ adjustForRowChanges?(shift: RowIndexShift): void;
34
45
  }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Where the view goes after new rows came in (`tableApi.appendRows()` / `rowsAppended()`):
3
+ * - `"auto"` (default): it follows the end if it was at the end before ("tail -f"), otherwise the first visible row
4
+ * stays where it is;
5
+ * - `true`: always to the end;
6
+ * - `false`: the first visible row always stays (a "freeze scroll" button of the app).
7
+ *
8
+ * While the mouse button is held in the table (a click, a drag), `"auto"` keeps the row – the content does not slide
9
+ * away under the pointer.
10
+ *
11
+ * @category Live data
12
+ * @since 1.1.108
13
+ */
14
+ export type AppendFollowMode = "auto" | boolean;
15
+ /**
16
+ * Options of `tableApi.appendRows(rows, options)`.
17
+ *
18
+ * @example
19
+ * const result = tableApi.appendRows(lines, { follow: "auto", maxRows: 1_000_000 });
20
+ *
21
+ * @category Live data
22
+ * @since 1.1.108
23
+ */
24
+ export interface AppendRowsOptionsIf {
25
+ /** Where the view goes – see {@link AppendFollowMode}. Default `"auto"`. */
26
+ follow?: AppendFollowMode;
27
+ /**
28
+ * Ring buffer: keep at most this many rows – the oldest (first) rows of the data are removed from the start, in
29
+ * blocks of at least 1 % of `maxRows` (removing from the start of an array moves all rows: O(n) – the blocks make
30
+ * that rare), so the row count stays between 99 % and 100 % of `maxRows`.
31
+ * Selection, focus and the visible row stay at their rows; a selected row that is removed leaves the selection.
32
+ */
33
+ maxRows?: number;
34
+ }
35
+ /**
36
+ * Options of `tableApi.rowsAppended(count, options)` – an own area model that appended (and maybe removed) rows
37
+ * itself.
38
+ *
39
+ * @example
40
+ * const logLines: string[] = []; // the data of an own area model
41
+ * for (const line of newLines) logLines.push(line);
42
+ * const dropped = Math.max(0, logLines.length - 100_000);
43
+ * logLines.splice(0, dropped); // the app keeps its own ring buffer
44
+ * tableApi.rowsAppended(newLines.length, { follow: "auto", removedAtStart: dropped });
45
+ *
46
+ * @category Live data
47
+ * @since 1.1.108
48
+ */
49
+ export interface RowsAppendedOptionsIf {
50
+ /** Where the view goes – see {@link AppendFollowMode}. Default `"auto"`. */
51
+ follow?: AppendFollowMode;
52
+ /** Number of rows the app removed at the start (display indices 0 … n-1 before the step). Default 0. */
53
+ removedAtStart?: number;
54
+ }
55
+ /**
56
+ * Result of `tableApi.appendRows()` / `rowsAppended()`.
57
+ *
58
+ * @category Live data
59
+ * @since 1.1.108
60
+ */
61
+ export interface AppendRowsResultIf {
62
+ /** rows added to the data */
63
+ added: number;
64
+ /** of them shown (an active filter hides the others) */
65
+ shown: number;
66
+ /** rows removed at the start (`maxRows` / `removedAtStart`) */
67
+ removed: number;
68
+ /** true if the view follows the end after this update */
69
+ following: boolean;
70
+ }
71
+ /**
72
+ * What a table passes to {@link AreaModelIf.appendRows}: the active filter and sort order, so the model treats only
73
+ * the new rows (O(k) unsorted, O(n + k) sorted).
74
+ *
75
+ * @category Live data
76
+ * @since 1.1.108
77
+ */
78
+ export interface AppendRowsContextIf<T = unknown> {
79
+ /** the active filter (`tableOptions.externalFilterFunction`) – new rows it rejects are kept but not shown */
80
+ filter?: (row: T) => boolean;
81
+ /** the active sort order – new rows are inserted at their place (stable: behind equal rows) */
82
+ compare?: (a: T, b: T) => number;
83
+ /** ring buffer size, see {@link AppendRowsOptionsIf.maxRows} */
84
+ maxRows?: number;
85
+ }
86
+ /**
87
+ * What {@link AreaModelIf.appendRows} did – the table moves selection, focus and position along.
88
+ *
89
+ * @category Live data
90
+ * @since 1.1.108
91
+ */
92
+ export interface AppendRowsModelResultIf {
93
+ added: number;
94
+ shown: number;
95
+ /** rows removed from the data */
96
+ removed: number;
97
+ /** display indices **before** the step of the removed rows that were shown, ascending */
98
+ removedPositions: Int32Array;
99
+ /** display indices **after** the step of the new shown rows, ascending */
100
+ insertedPositions: Int32Array;
101
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * How the display indices of the body rows move when rows are removed and inserted in one step (live data:
3
+ * `tableApi.appendRows()` / `rowsAppended()`, since 1.1.108) – so selection, focus and anchor stay at the same rows
4
+ * instead of being cleared. DOM-free.
5
+ *
6
+ * Order: first the `removed` rows leave (indices **before** the step), then the `inserted` rows come in (indices
7
+ * **after** the step). An old row keeps its relative order.
8
+ *
9
+ * @example
10
+ * // 2 rows dropped at the start (ring buffer), 3 appended at the end of 10 → 11 rows
11
+ * const shift = new RowIndexShift([0, 1], [8, 9, 10]);
12
+ * shift.mapIndex(5); // 3
13
+ * shift.mapIndex(1); // -1 (removed)
14
+ *
15
+ * @example
16
+ * // sorted: one row inserted at display index 2 (between the old rows 1 and 2)
17
+ * const shift = new RowIndexShift([], [2]);
18
+ * shift.mapRange(0, 4); // [[0, 1], [3, 5]] – the new row splits the range (it is not selected)
19
+ *
20
+ * @category Live data
21
+ * @since 1.1.108
22
+ */
23
+ export declare class RowIndexShift {
24
+ private readonly removed;
25
+ private readonly inserted;
26
+ /** inserted[j] - j: the number of old rows in front of the inserted row j (non-decreasing) */
27
+ private readonly insertBefore;
28
+ /**
29
+ * @param {ArrayLike<number>} removed - display indices before the step of the removed rows, ascending, unique
30
+ * @param {ArrayLike<number>} inserted - display indices after the step of the inserted rows, ascending, unique
31
+ */
32
+ constructor(removed?: ArrayLike<number>, inserted?: ArrayLike<number>);
33
+ /** True if nothing moves (no row removed, none inserted). */
34
+ isEmpty(): boolean;
35
+ /** Number of removed rows. */
36
+ getRemovedCount(): number;
37
+ /** Number of inserted rows. */
38
+ getInsertedCount(): number;
39
+ /**
40
+ * True if a row at an old display index `<= index` is removed or a row is inserted in front of the old row `index`
41
+ * (i.e. the rows `0 … index` are not the same as before).
42
+ *
43
+ * @param {number} index - an old display index
44
+ * @returns {boolean} true if the rows up to this index changed
45
+ */
46
+ touchesUpTo(index: number): boolean;
47
+ /**
48
+ * The new display index of an old one.
49
+ *
50
+ * @param {number} index - display index before the step
51
+ * @returns {number} the index after the step, -1 if the row was removed (or `index` is negative)
52
+ */
53
+ mapIndex(index: number): number;
54
+ /**
55
+ * The display index after the step of the first remaining row at or after an old index – for a position that
56
+ * must stay on a row (e.g. the first visible row) when that row itself was removed.
57
+ *
58
+ * @param {number} index - display index before the step
59
+ * @returns {number} the index after the step (the end if no old row at or after `index` remains)
60
+ */
61
+ mapIndexOrNext(index: number): number;
62
+ /**
63
+ * The new ranges of an old range of rows `lo … hi` (inclusive): removed rows leave it, inserted rows inside split
64
+ * it (they are not part of it). `hi = Number.MAX_SAFE_INTEGER` (whole columns) stays open at the end.
65
+ *
66
+ * @param {number} lo - first row of the range before the step
67
+ * @param {number} hi - last row of the range before the step (inclusive)
68
+ * @returns {[number, number][]} the pieces after the step, ascending, `[]` if all its rows were removed
69
+ */
70
+ mapRange(lo: number, hi: number): [number, number][];
71
+ }
@@ -1,5 +1,6 @@
1
1
  import { TableScope } from './table-scope.js';
2
2
  import { GeMouseEvent } from './data/common/event/ge-mouse-event.js';
3
+ import { RowIndexShift } from './live-append/row-index-shift.js';
3
4
 
4
5
  /**
5
6
  * Class representing a MouseHandler.
@@ -17,11 +18,26 @@ export declare class MouseHandler {
17
18
  * @type {number}
18
19
  */
19
20
  doubleClickDelay: number;
21
+ /**
22
+ * A render between mousedown and mouseup replaces the pressed cell, and the browser sends no "click" (both must hit
23
+ * the same element). The handler then reports the click itself for the pressed cell – if the mouse moved at most
24
+ * this many pixels (screen coordinates) between mousedown and mouseup. Since 1.1.108.
25
+ *
26
+ * @type {number}
27
+ */
28
+ clickMoveTolerance: number;
20
29
  mouseEvent?: MouseEvent;
21
30
  startMouseEvent?: GeMouseEvent;
22
31
  geMouseEvent?: GeMouseEvent;
23
32
  geMouseEventOld?: GeMouseEvent;
24
33
  private lastClickedCellKey;
34
+ /** The cell of the last left-button mousedown (see {@link PressedCell}). */
35
+ private pressedCell?;
36
+ /** Counts native "click" / "dblclick" events: a report of a lost click is dropped if the browser sent one after all. */
37
+ private nativeClickCount;
38
+ private nativeDblClickCount;
39
+ /** Timer of a pending report of a lost click (cleared by destroy via the abort signal). */
40
+ private lostClickTimer?;
25
41
  /** The second click of a double click that was already reported (clickCount 2) – its "dblclick" is not. */
26
42
  private reportedDoubleClick?;
27
43
  private mouseDown;
@@ -49,6 +65,16 @@ export declare class MouseHandler {
49
65
  * @return {void}
50
66
  */
51
67
  private onHostElementClicked;
68
+ /**
69
+ * Handles a click: on `eventTarget`, or – if `cell` is given – on that cell (a click the browser did not send
70
+ * because a render replaced the pressed element; `eventTarget` is then the detached pressed element).
71
+ */
72
+ private handleClick;
73
+ /** "area:row:column" of a cell given explicitly (same format as {@link getCellKey}). */
74
+ private toCellKey;
75
+ /** Target data of a replaced element: area, row and column come from the pressed cell (current row index). */
76
+ private applyCell;
77
+ private createCellEvent;
52
78
  /** "area:row:column" of the cell of an event target ('' outside of a cell). */
53
79
  private getCellKey;
54
80
  /** A click that triggers an action on its own: row checkbox, tree arrow, "expand/collapse all", header group toggle. */
@@ -60,6 +86,8 @@ export declare class MouseHandler {
60
86
  * @param {MouseEvent} event - The double click event.
61
87
  */
62
88
  private onHostElementDblClicked;
89
+ /** Handles a double click on `eventTarget`, or on `cell` (see {@link handleClick}). */
90
+ private handleDblClick;
63
91
  /**
64
92
  * Publishes a GeMouseEvent.
65
93
  *
@@ -77,12 +105,41 @@ export declare class MouseHandler {
77
105
  private isInteractiveCellElement;
78
106
  private getArrowColumnIndex;
79
107
  private onMouseDown;
108
+ /** A mouseup that did not reach the host (released outside the table). */
109
+ private onDocumentMouseUp;
110
+ /**
111
+ * Live data (since 1.1.108): a drag in progress (range selection) keeps its start row when rows were removed at the
112
+ * start or inserted.
113
+ *
114
+ * @param {RowIndexShift} shift - how the display indices move
115
+ */
116
+ adjustForRowChanges(shift: RowIndexShift): void;
80
117
  private onMouseMove;
81
118
  private onMouseUp;
119
+ /** The cell under a mousedown, or undefined outside of a cell. */
120
+ private readPressedCell;
121
+ /**
122
+ * After a mouseup: if a render replaced the pressed element, the browser sends no "click" (nor "dblclick"). Then the
123
+ * click is reported for the pressed cell – once this task is through and only if no native click came after all.
124
+ */
125
+ private checkLostClick;
126
+ /**
127
+ * The current index of the pressed row: rows may have been added, removed or sorted in while the button was held.
128
+ * -1 if the row is gone.
129
+ */
130
+ private resolveRowIndex;
131
+ private rowAt;
82
132
  private mouseDraggingOnFrame;
83
133
  /** Handles the end of the last drag now (in its frame, or earlier when the next mousedown comes first). */
84
134
  private flushDraggingEnd;
85
135
  private mouseMoveOnFrame;
136
+ /**
137
+ * True while the mouse button is held in the table (a click or a drag in progress) – live data does not jump to
138
+ * the end then (`tableApi.appendRows({ follow: "auto" })`, since 1.1.108).
139
+ *
140
+ * @returns {boolean} true between mousedown and mouseup
141
+ */
142
+ isButtonDown(): boolean;
86
143
  /** A mouse event on a blocking loading overlay ({@link TableOverlay.isEventBlocked}): the table ignores it. */
87
144
  private isBlocked;
88
145
  }
@@ -11,6 +11,7 @@ import { CellRange } from './data/common/cell-range.js';
11
11
  import { RendererCleanupFnType } from './renderer/renderer-cleanup-fn.type.js';
12
12
  import { AreaIdent } from './data/tablemodel/area-ident.type.js';
13
13
  import { SideIdent } from './data/side-ident.type.js';
14
+ import { RowIndexShift } from './live-append/row-index-shift.js';
14
15
  import { DivScope } from './data/div-scope.type.js';
15
16
  import { DerivedValuesChangeIf } from './data/tablemodel/areamodel/derived-values-change.if.js';
16
17
  import { AreaModelIf } from './data/tablemodel/areamodel/area-model.if.js';
@@ -73,6 +74,17 @@ export declare class RenderScope extends EleScope {
73
74
  * that is already rendered (`repaintHard({ py })`, `scrollToPixel()` render it at once) renders nothing.
74
75
  */
75
76
  protected renderedScrollTop: number;
77
+ /**
78
+ * The body rows of the last render pass (live data, since 1.1.108): row count, first visible row and the content
79
+ * offset into it, last (partly) visible row, and whether the view was at the end.
80
+ */
81
+ protected renderedBodyRows: {
82
+ count: number;
83
+ first: number;
84
+ offset: number;
85
+ last: number;
86
+ atEnd: boolean;
87
+ };
76
88
  protected renderedScrollLeft: number;
77
89
  /** Area heights read once at the start of adjustAfterScrolling() (before the DOM is modified). */
78
90
  protected areaHeightsOfRenderPass?: {
@@ -273,6 +285,13 @@ export declare class RenderScope extends EleScope {
273
285
  *
274
286
  * @returns {void}
275
287
  */
288
+ /**
289
+ * Live data (since 1.1.108): the in-cell editor (`getEditRenderer`) and its draft follow their row when rows were
290
+ * removed at the start or inserted; a removed row closes the editor.
291
+ *
292
+ * @param shift - how the display indices move
293
+ */
294
+ protected shiftEditorRow(shift: RowIndexShift): void;
276
295
  resetEditorRenderer(): void;
277
296
  /**
278
297
  * Clears the selection in the component.
@@ -313,6 +332,18 @@ export declare class RenderScope extends EleScope {
313
332
  * @return {void} This method does not return any value.
314
333
  */
315
334
  repaintHard(scrollTo?: ScrollPositionIf): void;
335
+ /** Remembers which body rows the render pass drew ({@link renderedBodyRows}) – two O(log n) lookups. */
336
+ private rememberRenderedBodyRows;
337
+ /**
338
+ * Live data (since 1.1.108, prompt 018 part B): the body got more rows, but the drawn rows stay the same (rows
339
+ * appended or sorted in behind the visible ones, the view does not follow). Updates heights, scroll content, scroll
340
+ * bar and `aria-rowcount` – **without** rebuilding the cells. Returns false (and does nothing more) if that is not
341
+ * enough: the vertical scroll bar appeared or disappeared (the widths change) or col-/rowspans exist.
342
+ *
343
+ * @param {number} py - the content position to keep (the same rows as drawn)
344
+ * @returns {boolean} true if done, false if the caller must render
345
+ */
346
+ protected updateRowCountWithoutRender(py: number): boolean;
316
347
  /**
317
348
  * Sets the scroll position of the viewport to a content position **without rendering** (shared by
318
349
  * `scrollToPixel()` and `repaintHard({ px, py })`). `py` is a content position: the scroll bar range differs from the
@@ -2,6 +2,7 @@ import { CellRange } from '../data/common/cell-range.js';
2
2
  import { ExtendedSelectionType, SelectionMode } from './selection.type.js';
3
3
  import { SelectionModelIf } from './selection-model.if.js';
4
4
  import { EventSelectionChangedListenerIf } from './event-selection-changed-listener.if.js';
5
+ import { RowIndexShift } from '../live-append/row-index-shift.js';
5
6
 
6
7
  /**
7
8
  * Saved state of a {@link SelectionModel} – see {@link SelectionModel.createSnapshot}.
@@ -84,6 +85,22 @@ export declare class SelectionModel implements SelectionModelIf {
84
85
  * @param {SelectionSnapshot} snapshot - the state to restore
85
86
  */
86
87
  restoreSnapshot(snapshot: SelectionSnapshot): void;
88
+ /**
89
+ * Live data (since 1.1.108): moves the selected rows along when rows were removed at the start or inserted
90
+ * (`tableApi.appendRows()` / `rowsAppended()`). Ranges are adjusted **in place** (snapshots and the anchor of the
91
+ * selection service keep pointing at them); removed rows leave a range, rows inserted inside split it (not
92
+ * selected – like Excel); whole columns (`r1 = 0`, `r2 = MAX_SAFE_INTEGER`) and "all selected" stay. No event.
93
+ *
94
+ * @param {RowIndexShift} shift - how the display indices move
95
+ * @returns {boolean} true if a selected row was removed
96
+ *
97
+ * @example
98
+ * // rows 0 and 1 removed (ring buffer): the selection of row 5 is row 3 now
99
+ * selectionModel.adjustForRowChanges(new RowIndexShift([0, 1], []));
100
+ */
101
+ adjustForRowChanges(shift: RowIndexShift): boolean;
102
+ /** Shifts ranges in place (split pieces are appended); true if rows of a range were removed. */
103
+ private static shiftRanges;
87
104
  getSelectionCount(rowIndex: number, columnIndex: number): number;
88
105
  isInNegativeRange(rowIndex: number, columnIndex: number): boolean;
89
106
  getRanges(): CellRange[];
@@ -1,5 +1,6 @@
1
1
  import { CellRange } from '../data/common/cell-range.js';
2
2
  import { EventSelectionChangedListenerIf } from './event-selection-changed-listener.if.js';
3
+ import { RowIndexShift } from '../live-append/row-index-shift.js';
3
4
 
4
5
  /**
5
6
  * What is selected in a table – a list of rectangles ({@link CellRange}). The implementation is {@link SelectionModel};
@@ -82,4 +83,16 @@ export interface SelectionModelIf {
82
83
  isAllSelected(): boolean;
83
84
  /** Called by the table on a click: starts a selection at this cell, depending on selection type and mode. */
84
85
  firstClick(rowIndex: number, columnIndex: number): void;
86
+ /**
87
+ * Optional (live data, since 1.1.108): moves the selected rows along when rows were removed at the start or
88
+ * inserted (`tableApi.appendRows()` / `rowsAppended()`), instead of clearing the selection. Removed rows leave the
89
+ * selection; rows inserted inside a selected range split it (they are not selected); whole columns and "all
90
+ * selected" stay. Fires no event.
91
+ *
92
+ * @param {RowIndexShift} shift - how the display indices move
93
+ * @returns {boolean} true if a selected row was removed (the selection shows other rows now)
94
+ * @example
95
+ * selectionModel.adjustForRowChanges?.(new RowIndexShift([0, 1], [98, 99]));
96
+ */
97
+ adjustForRowChanges?(shift: RowIndexShift): boolean;
85
98
  }
@@ -7,6 +7,7 @@ import { CellRange } from '../data/common/cell-range.js';
7
7
  import { OnActionTriggeredIf } from '../action/on-action-triggered.if.js';
8
8
  import { ActionId } from '../action/action-id.type.js';
9
9
  import { SelectionSnapshot } from './selection-model.js';
10
+ import { RowIndexShift } from '../live-append/row-index-shift.js';
10
11
 
11
12
  /** @category Internals */
12
13
  export declare class SelectionService implements OnActionTriggeredIf {
@@ -62,6 +63,13 @@ export declare class SelectionService implements OnActionTriggeredIf {
62
63
  * @returns {[number, number] | undefined} row and column, `undefined` before the first click/keyboard move
63
64
  * @since 1.1.99
64
65
  */
66
+ /**
67
+ * Live data (since 1.1.108): anchor, moving corner and last click follow their rows when rows were removed at the
68
+ * start or inserted (`tableApi.appendRows()`); a removed row drops them.
69
+ *
70
+ * @param {RowIndexShift} shift - how the display indices move
71
+ */
72
+ adjustForRowChanges(shift: RowIndexShift): void;
65
73
  getAnchor(): [number, number] | undefined;
66
74
  /**
67
75
  * The moving corner of the range Shift+arrows grow (the anchor if there is none).