@guiexpert/table 1.1.106 → 1.1.108

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.
@@ -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';
@@ -30,6 +31,7 @@ import { AriaTextsIf } from './aria/aria-texts.if.js';
30
31
  import { RowLayout } from './row-layout/row-layout.js';
31
32
  import { MasterDetailService } from './master-detail/master-detail-service.js';
32
33
  import { TableSheet } from './sheet/table-sheet.js';
34
+ import { ScrollPositionIf } from './data/common/scroll-position.if.js';
33
35
 
34
36
  /** @category Internals */
35
37
  export declare class RenderScope extends EleScope {
@@ -67,6 +69,23 @@ export declare class RenderScope extends EleScope {
67
69
  scrollTop: number;
68
70
  contentTop: number;
69
71
  };
72
+ /**
73
+ * The scroll position (`scrollTop`, logical `scrollLeft`) of the last render pass – the `scroll` event of a position
74
+ * that is already rendered (`repaintHard({ py })`, `scrollToPixel()` render it at once) renders nothing.
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
+ };
88
+ protected renderedScrollLeft: number;
70
89
  /** Area heights read once at the start of adjustAfterScrolling() (before the DOM is modified). */
71
90
  protected areaHeightsOfRenderPass?: {
72
91
  [key in AreaIdent]?: number;
@@ -214,8 +233,10 @@ export declare class RenderScope extends EleScope {
214
233
  /** True if open detail rows take space between the body rows. */
215
234
  protected hasBodyRowExtras(): boolean;
216
235
  /**
217
- * The top of a body row in content pixels: {@link AreaModelIf.getYPosByRowIndex} plus the heights of the open detail
218
- * rows above it (master/detail).
236
+ * The top of a body row in content pixels – from the row layout of the table ({@link getBodyRowLayout}: the heights
237
+ * the rows are drawn with, plus the open detail rows above it). Since 1.1.107 always from the layout (before: without
238
+ * detail rows from {@link AreaModelIf.getYPosByRowIndex}, which an area model with its own `getRowHeight()` could
239
+ * get wrong – `scrollToIndex()` landed beside the row). O(1) for constant heights, otherwise after one O(n) build.
219
240
  *
220
241
  * @param {number} rowIndex - display index
221
242
  * @returns {number} px from the top of the first body row
@@ -264,6 +285,13 @@ export declare class RenderScope extends EleScope {
264
285
  *
265
286
  * @returns {void}
266
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;
267
295
  resetEditorRenderer(): void;
268
296
  /**
269
297
  * Clears the selection in the component.
@@ -296,13 +324,51 @@ export declare class RenderScope extends EleScope {
296
324
  */
297
325
  invalidateAggregates(change?: DerivedValuesChangeIf): void;
298
326
  /**
299
- * Repaints the UI by resetting the size of the wrapper div,
300
- * adjusting the containers and rows, and performing additional adjustments
301
- * after scrolling.
327
+ * Repaints the UI after the row count or the row heights changed: new heights and padding, new size of the scroll
328
+ * content, new layout of the areas – then **one** render pass (since 1.1.107; before: two).
302
329
  *
330
+ * @param {ScrollPositionIf} [scrollTo] - a content position to show in that render pass (like {@link scrollToPixel},
331
+ * exact also with a capped scroll height); a missing axis keeps its position
303
332
  * @return {void} This method does not return any value.
304
333
  */
305
- repaintHard(): void;
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;
347
+ /**
348
+ * Sets the scroll position of the viewport to a content position **without rendering** (shared by
349
+ * `scrollToPixel()` and `repaintHard({ px, py })`). `py` is a content position: the scroll bar range differs from the
350
+ * content range – always with a capped scroll height, but also slightly without (the scroll content is 1 px higher
351
+ * than the rows, the scroll viewport includes the footer and the horizontal scroll bar). `scrollTop = py` would leave
352
+ * the target row a few pixels off, so the exact content position is remembered (`pendingContentScroll`) and used by
353
+ * the next render passes as long as the scroll bar is not moved.
354
+ *
355
+ * @param {number | undefined} px - from the start of the rows (logical, right-to-left from the right edge);
356
+ * `undefined` keeps the horizontal position
357
+ * @param {number | undefined} py - content px from the top of the first body row; `undefined` keeps the vertical
358
+ * position
359
+ * @returns {boolean} true if the exact content position was set (then render now – a scroll event may not fire);
360
+ * false if the browser renders it with its scroll event (nothing to scroll vertically, or only `px`)
361
+ * @since 1.1.107
362
+ */
363
+ protected setContentScrollPosition(px: number | undefined, py: number | undefined): boolean;
364
+ /**
365
+ * The `scroll` listener of the scroll viewport: renders the new position – nothing if the position is already
366
+ * rendered (the event of a `repaintHard({ py })`, `scrollToPixel()` or `ensureColumnIsVisible()` arrives after
367
+ * their own render pass).
368
+ *
369
+ * @since 1.1.107
370
+ */
371
+ onViewportScroll(): void;
306
372
  /**
307
373
  * Recalculates the column widths of the table (% and weight columns depend on the available width) and repaints.
308
374
  *
@@ -316,7 +382,7 @@ export declare class RenderScope extends EleScope {
316
382
  protected setParentWidth(widthInPixel: number): void;
317
383
  /** True if a column has a % or weight width (it depends on the width of the host). */
318
384
  protected hasRelativeColumnWidths(): boolean;
319
- adjustContainersAndRows(): void;
385
+ protected layoutContainers(): void;
320
386
  /**
321
387
  * % and weight columns share the width next to the vertical scroll bar. The bar appears (or disappears) only after
322
388
  * the rows are laid out – the first layout, rows added or removed, a filter: then the columns were computed for the
@@ -448,14 +514,14 @@ export declare class RenderScope extends EleScope {
448
514
  * scaled and scrollTop is no longer a content pixel position.
449
515
  */
450
516
  isScrollHeightCapped(): boolean;
517
+ /** The detail layer (master/detail) covers the visible body. @since 1.1.97 */
518
+ protected adjustDetailLayerBounds(left: number, top: number, width: number, height: number): void;
451
519
  /**
452
520
  * The model's content height plus the heights of open detail rows (master/detail).
453
521
  *
454
522
  * @returns {number} px
455
523
  * @since 1.1.97
456
524
  */
457
- /** The detail layer (master/detail) covers the visible body. @since 1.1.97 */
458
- protected adjustDetailLayerBounds(left: number, top: number, width: number, height: number): void;
459
525
  protected getBodyContentHeight(): number;
460
526
  /** Scrollable range of the content in content pixels (content height minus visible body height). */
461
527
  protected getContentScrollRangeY(): number;
@@ -601,7 +667,7 @@ export declare class RenderScope extends EleScope {
601
667
  */
602
668
  private rowNumberWidth?;
603
669
  /** true while the containers are adjusted to a new row number width (no second change in that nested pass). */
604
- private adjustingRowNumberWidth;
670
+ protected adjustingRowNumberWidth: boolean;
605
671
  /** Guards {@link adjustRelativeColumnWidthsToScrollBar} against re-entry. */
606
672
  private adjustingRelativeWidths;
607
673
  /** The width % and weight columns were last computed for (`TableModelIf.setParentWidth`), -1 = unknown. */
@@ -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).
@@ -27,6 +27,8 @@ import { CellRange } from './data/common/cell-range.js';
27
27
  import { EventListenerIf } from './event-listener.if.js';
28
28
  import { ExcelExportOptionsIf } from './service/excel-export-options.if.js';
29
29
  import { SheetEngine } from './sheet/sheet-engine.js';
30
+ import { ScrollPositionIf } from './data/common/scroll-position.if.js';
31
+ import { AppendRowsOptionsIf, AppendRowsResultIf, RowsAppendedOptionsIf } from './live-append/append-rows-options.if.js';
30
32
 
31
33
  /**
32
34
  * The public API of one table – everything you do with a table after it has been created:
@@ -200,7 +202,7 @@ export declare class TableApi {
200
202
  * };
201
203
  *
202
204
  * // Initialize your table with these options
203
- * const tableApi = new TableApi(tableScope);
205
+ * const tableApi = tableScope.getApi();
204
206
  *
205
207
  * // Later, when filter criteria change (e.g., in a search input handler)
206
208
  * function onFilterTextChanged() {
@@ -432,7 +434,7 @@ export declare class TableApi {
432
434
  *
433
435
  * @example
434
436
  * // Update data and repaint the table
435
- * const tableApi = new TableApi(tableScope);
437
+ * const tableApi = tableScope.getApi();
436
438
  * tableApi.setRows(newData);
437
439
  * tableApi.repaint();
438
440
  *
@@ -464,19 +466,24 @@ export declare class TableApi {
464
466
  */
465
467
  destroy(): void;
466
468
  /**
467
- * Performs a complete repaint of the table by triggering a hard repaint on the underlying table scope.
469
+ * Performs a complete repaint of the table: use it when the row count or the row heights changed (rows added or
470
+ * removed, a filter, a new model content, `ColumnDefIf.isVisible` changed yourself …). It
471
+ * - drops cached derived values (footer totals, calculated columns),
472
+ * - recalculates the heights and the padding of the table model and the size of the scroll content,
473
+ * - lays out header, body and footer again and
474
+ * - renders the visible rows – **once** (since 1.1.107; before: twice).
468
475
  *
469
- * A hard repaint is more thorough than a regular repaint and performs the following operations:
470
- * - Recalculates the height and padding of the table model
471
- * - Resets the size of the wrapper div elements
472
- * - Adjusts containers and rows positioning
473
- * - Makes additional adjustments after scrolling
476
+ * With `scrollTo` (since 1.1.107) it also sets the scroll position in that same render pass – exactly like
477
+ * {@link scrollToPixel}, also with a capped scroll height (more than {@link EleScope.maxScrollHeightInPx} px of rows).
478
+ * A live tail that appends rows and keeps a position needs one render per update instead of three
479
+ * (`repaintHard()` + `scrollToPixel()`). The `scroll` event of the browser for that position renders nothing.
480
+ * A missing axis keeps its position.
474
481
  *
475
- * Use this method when regular repaint isn't sufficient, such as when:
476
- * - Table dimensions have significantly changed
477
- * - The structure of data has been modified (rows/columns added or removed)
478
- * - The table layout needs complete recalculation
479
- * - After major DOM changes affecting the table's container
482
+ * For a repaint with the same row count (values changed) {@link repaint} is enough.
483
+ *
484
+ * @param {ScrollPositionIf} [scrollTo] - the position to show: `px` horizontal scroll position from the start, `py`
485
+ * content pixels from the top of the first body row (both like {@link scrollToPixel}); without it the position stays
486
+ * @returns {void} This method doesn't return a value
480
487
  *
481
488
  * @example
482
489
  * // Example 1: Basic usage
@@ -499,9 +506,20 @@ export declare class TableApi {
499
506
  * column.isVisible = FalseFn;
500
507
  * tableApi.repaintHard();
501
508
  *
502
- * @returns {void} This method doesn't return a value
509
+ * @example
510
+ * // Example 5: live tail – rows were appended to the area model, the first visible row stays at the top
511
+ * // (pixel-exact: keep your own py from the last update instead – e.g. from onScroll)
512
+ * const rowHeight = 20; // the row height of the area model
513
+ * const first = tableApi.getFirstVisibleRowIndex();
514
+ * rows.push(...newData); // the row count of the model grows
515
+ * tableApi.repaintHard({ py: first * rowHeight }); // one render pass, exact also for millions of rows
516
+ *
517
+ * @example
518
+ * // Example 6: follow the end ("tail -f") – show the last row after every update
519
+ * rows.push(...newData);
520
+ * tableApi.repaintHard({ py: Number.MAX_SAFE_INTEGER }); // clamped to the end of the content
503
521
  */
504
- repaintHard(): void;
522
+ repaintHard(scrollTo?: ScrollPositionIf): void;
505
523
  /**
506
524
  * Recalculates the column widths of the table.
507
525
  *
@@ -888,7 +906,7 @@ export declare class TableApi {
888
906
  *
889
907
  * @example
890
908
  * // Access the TableScope to perform a custom operation
891
- * const tableApi = new TableApi(myTableScope);
909
+ * const tableApi = myTableScope.getApi();
892
910
  * const tableScope = tableApi.getTableScope();
893
911
  *
894
912
  * // Example: Manually trigger a context menu at a specific location
@@ -1023,6 +1041,8 @@ export declare class TableApi {
1023
1041
  * Only works with an {@link AreaModelObjectArray} body – logs a console warning otherwise.
1024
1042
  * Does **not** repaint and resets filtering and sorting of the body: afterwards call
1025
1043
  * {@link externalFilterChanged} (if a filter function is set) and {@link repaintHard} (the row count changed).
1044
+ * Copies all rows (O(n)) – for live data (rows arriving all the time) use {@link appendRows}: in place, filter and
1045
+ * order only for the new rows, selection and position stay.
1026
1046
  *
1027
1047
  * @template T - The type of elements in the rows array
1028
1048
  * @param {T[]} rows - An array of data objects to append to the table's existing rows
@@ -1039,6 +1059,81 @@ export declare class TableApi {
1039
1059
  * tableApi.repaintHard();
1040
1060
  * tableApi.ensureRowIsVisible(tableApi.getDisplayedRowCount() - 1);
1041
1061
  */
1062
+ /**
1063
+ * **Live data** (a log tail, a ticker – since 1.1.108): appends rows to the body and shows them in **one** render
1064
+ * pass – O(k) for k new rows, not O(n).
1065
+ *
1066
+ * - **In place**: the rows are pushed to your array (`getAllRows()`), nothing is copied.
1067
+ * - **Filter and order** (`externalFilterFunction`, a sorted column or `sort(fn)`) apply to the new rows only:
1068
+ * filtered rows are kept but not shown, sorted rows are merged in (O(n + k), behind equal rows).
1069
+ * - **Selection, focus, anchor, checked rows** stay at their rows (the indices move along; no `onSelectionChanged`
1070
+ * unless a selected row was removed).
1071
+ * - **Position**: `follow: "auto"` (default) stays at the end if the view was at the end ("tail -f"), otherwise the
1072
+ * first visible row stays; `true` = always the end, `false` = always keep the row ("freeze scroll"). While the
1073
+ * mouse button is held in the table, `"auto"` keeps the row.
1074
+ * - **Ring buffer**: `maxRows` removes the oldest rows from the start.
1075
+ * - Rows behind the visible ones (a reader in the middle of the log): the cells are not rebuilt – only scroll height
1076
+ * and scroll bar change.
1077
+ *
1078
+ * Works with an {@link AreaModelObjectArray} body (`TableFactory.createTableModel({ rows, … })`) without cell
1079
+ * formulas; a tree, row grouping, pivot or spreadsheet → `false` and a console warning. Own area model:
1080
+ * {@link rowsAppended}.
1081
+ *
1082
+ * @param {T[]} rows - the new rows (appended in this order)
1083
+ * @param {AppendRowsOptionsIf} [options] - `follow` ("auto" | true | false), `maxRows`
1084
+ * @returns {AppendRowsResultIf | false} `{ added, shown, removed, following }`, or false
1085
+ *
1086
+ * @example
1087
+ * // a log viewer: new lines every animation frame, at most one million
1088
+ * const result = tableApi.appendRows(newLines, { follow: "auto", maxRows: 1_000_000 });
1089
+ * if (result && !result.following) {
1090
+ * console.log(`+${result.shown} new lines below`);
1091
+ * }
1092
+ *
1093
+ * @example
1094
+ * // "Freeze scroll" / "Scroll to end" buttons of the app
1095
+ * let frozen = false;
1096
+ * function onLines(lines: { time: string; text: string }[]) {
1097
+ * tableApi.appendRows(lines, { follow: frozen ? false : "auto" });
1098
+ * }
1099
+ * function scrollToEnd() {
1100
+ * frozen = false;
1101
+ * tableApi.repaintHard({ py: Number.MAX_SAFE_INTEGER });
1102
+ * }
1103
+ *
1104
+ * @since 1.1.108
1105
+ */
1106
+ appendRows<T>(rows: T[], options?: AppendRowsOptionsIf): AppendRowsResultIf | false;
1107
+ /**
1108
+ * **Live data with an own area model** (since 1.1.108): your model appended `count` rows at the end (and maybe
1109
+ * removed `removedAtStart` rows at the start) – the table moves selection, focus and anchor along, keeps the first
1110
+ * visible row or follows the end (`follow` as in {@link appendRows}) and renders once (not at all if only rows behind
1111
+ * the visible ones came in). Filtering and sorting are up to your model.
1112
+ *
1113
+ * @param {number} count - rows appended at the end
1114
+ * @param {RowsAppendedOptionsIf} [options] - `follow`, `removedAtStart`
1115
+ * @returns {AppendRowsResultIf} `{ added, shown, removed, following }`
1116
+ *
1117
+ * @example
1118
+ * class LogBody extends AreaModel {
1119
+ * readonly lines: string[] = [];
1120
+ * override getRowCount(): number { return this.lines.length; }
1121
+ * override getRowHeight(): number { return 20; }
1122
+ * override getConstantRowHeight(): number { return 20; }
1123
+ * override getValueAt(row: number): string { return this.lines[row]; }
1124
+ * }
1125
+ * const body = new LogBody();
1126
+ *
1127
+ * function onNewLines(newLines: string[]) {
1128
+ * for (const line of newLines) body.lines.push(line); // a loop: push(...lines) overflows above ~100,000
1129
+ * const dropped = Math.max(0, body.lines.length - 1_000_000);
1130
+ * body.lines.splice(0, dropped); // the app's own ring buffer
1131
+ * tableApi.rowsAppended(newLines.length, { follow: "auto", removedAtStart: dropped });
1132
+ * }
1133
+ *
1134
+ * @since 1.1.108
1135
+ */
1136
+ rowsAppended(count: number, options?: RowsAppendedOptionsIf): AppendRowsResultIf;
1042
1137
  addRows<T>(rows: T[]): void;
1043
1138
  /**
1044
1139
  * Inserts new rows at a specific position in the table body.
@@ -2325,7 +2420,7 @@ export declare class TableApi {
2325
2420
  * @example
2326
2421
  * ```typescript
2327
2422
  * // Get the first visible row index
2328
- * const tableApi = new TableApi(tableScope);
2423
+ * const tableApi = tableScope.getApi();
2329
2424
  * const firstVisibleIndex = tableApi.getFirstVisibleRowIndex();
2330
2425
  *
2331
2426
  * // Use the index for scroll position calculations
@@ -2,6 +2,7 @@ import { TableOverlayChange } from './overlay/table-overlay.js';
2
2
  import { GeOverlayKind } from './overlay/ge-overlay-kind.type.js';
3
3
  import { TableModelIf } from './data/tablemodel/table-model.if.js';
4
4
  import { AreaIdent } from './data/tablemodel/area-ident.type.js';
5
+ import { SideIdent } from './data/side-ident.type.js';
5
6
  import { GeMouseEvent } from './data/common/event/ge-mouse-event.js';
6
7
  import { DomServiceIf } from './service/dom-service.if.js';
7
8
  import { TableOptionsIf } from './data/options/table-options.if.js';
@@ -50,6 +51,7 @@ import { ClipboardService } from './clipboard/clipboard-service.js';
50
51
  import { UndoOptionsIf } from './undo/undo-options.if.js';
51
52
  import { UndoService } from './undo/undo-service.js';
52
53
  import { ShortcutActionIdMapping } from './action/shortcut-actionid-mapping.type.js';
54
+ import { AppendRowsOptionsIf, AppendRowsResultIf, RowsAppendedOptionsIf } from './live-append/append-rows-options.if.js';
53
55
 
54
56
  /**
55
57
  * Creates and runs one table in a host element – the core that every framework wrapper uses.
@@ -130,6 +132,8 @@ export declare class TableScope extends RenderScope implements OnActionTriggered
130
132
  private firstDraggingRendering;
131
133
  /** Last comparator passed to `sort()`; re-applied after filtering. Cleared by a header sort. */
132
134
  private lastSortCompareFn?;
135
+ /** Live data: the content position held while the mouse button was down in a view that followed the end. */
136
+ private followHeldAt?;
133
137
  /** Excel-style editing (`tableOptions.editing`), `undefined` = off. @since 1.1.99 */
134
138
  private readonly editingOptions?;
135
139
  /** Listeners of {@link TableApi.subscribe}. @since 1.1.99 */
@@ -750,6 +754,22 @@ export declare class TableScope extends RenderScope implements OnActionTriggered
750
754
  * @return {GeMouseEvent} - The created GeMouseEvent object.
751
755
  */
752
756
  createGeMouseEvent(mouseEvent: MouseEvent): GeMouseEvent;
757
+ /**
758
+ * Creates a GeMouseEvent for a given cell – used when the cell element of a mouse event is gone (a render replaced
759
+ * it between mousedown and mouseup, see {@link MouseHandler}) and the cell is known from the mousedown.
760
+ *
761
+ * @param mouseEvent - the original browser event
762
+ * @param cell - area, side, row and column of the cell, and the action of the pressed element
763
+ * @returns the event with row/column positions of the cell
764
+ * @since 1.1.108
765
+ */
766
+ createGeMouseEventForCell(mouseEvent: MouseEvent, cell: {
767
+ areaIdent?: AreaIdent;
768
+ sideIdent?: SideIdent;
769
+ rowIndex: number;
770
+ columnIndex: number;
771
+ action: string | null;
772
+ }): GeMouseEvent;
753
773
  /**
754
774
  * The element a mouse event belongs to – usually `evt.target`. The scroll viewport lies above the footer areas (its
755
775
  * horizontal scroll bar must stay usable), so a mouse event on a footer cell arrives at the viewport: then the footer
@@ -1039,6 +1059,37 @@ export declare class TableScope extends RenderScope implements OnActionTriggered
1039
1059
  private setAreaVisible;
1040
1060
  autoResizeColumns(recalcWrappers?: boolean): void;
1041
1061
  recalcWrappers(): void;
1062
+ /**
1063
+ * Live data (since 1.1.108): appends rows to an {@link AreaModelObjectArray} body in place – filter and sort order
1064
+ * only for the new rows, optional ring buffer – moves selection, focus and anchor along, keeps the first visible row
1065
+ * or follows the end, and renders once (not at all if only rows behind the visible ones came in).
1066
+ * See {@link TableApi.appendRows}.
1067
+ *
1068
+ * @param {T[]} rows - the new rows
1069
+ * @param {AppendRowsOptionsIf} [options] - follow mode, ring buffer
1070
+ * @returns {AppendRowsResultIf | false} what changed; false (with a warning) for other bodies (tree, row groups,
1071
+ * pivot, spreadsheet, cell formulas)
1072
+ */
1073
+ appendRows<T>(rows: T[], options?: AppendRowsOptionsIf): AppendRowsResultIf | false;
1074
+ /**
1075
+ * Live data (since 1.1.108): an own area model appended `count` rows at the end (and maybe removed some at the
1076
+ * start) – moves selection, focus and anchor along, keeps the first visible row or follows the end, renders once.
1077
+ * See {@link TableApi.rowsAppended}.
1078
+ *
1079
+ * @param {number} count - rows the model appended at the end (display order)
1080
+ * @param {RowsAppendedOptionsIf} [options] - follow mode, rows removed at the start
1081
+ * @returns {AppendRowsResultIf} what changed
1082
+ */
1083
+ notifyRowsAppended(count: number, options?: RowsAppendedOptionsIf): AppendRowsResultIf;
1084
+ /** The order new rows are sorted in: the last `sort(fn)`, otherwise the columns with a sort state. */
1085
+ protected getActiveSortComparator(): ((a: unknown, b: unknown) => number) | undefined;
1086
+ /** Moves the index-based state along, chooses the position and renders once (or only updates the row count). */
1087
+ private afterRowsAppended;
1088
+ /**
1089
+ * True if nothing else on the screen depends on the row count: no row number column (its width may grow), no
1090
+ * totals footer, no calculated/„show values as“ columns, no open detail rows.
1091
+ */
1092
+ private canKeepDrawnRows;
1042
1093
  reSort(): void;
1043
1094
  sort(compareFn: (a: any, b: any) => number): void;
1044
1095
  /**