@guiexpert/table 1.1.109 → 1.1.110

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.
@@ -125,6 +125,8 @@ export declare class TableScope extends RenderScope implements OnActionTriggered
125
125
  private mouseStartAction;
126
126
  private mouseStartWidth;
127
127
  private mouseStartColumnIndex;
128
+ /** The column resize or move of the current press (prompt 022) – `undefined` without an allowed column action. */
129
+ private columnDrag?;
128
130
  private dragFrom;
129
131
  private dragTo;
130
132
  private lastDragFrom;
@@ -786,6 +788,32 @@ export declare class TableScope extends RenderScope implements OnActionTriggered
786
788
  * @return {void}
787
789
  */
788
790
  onMouseDown(mouseEvent: GeMouseEvent): void;
791
+ /** Forgets the column action of the last press (no events). */
792
+ private resetColumnDrag;
793
+ /**
794
+ * Starts the column drag of this press once the mouse moved past {@link MouseHandler.clickMoveTolerance}: reports
795
+ * `onColumnResizeStart` / `onColumnMoveStart` and listens for Escape.
796
+ *
797
+ * @returns true if the drag runs (started now or before), false below the threshold, without a drag or cancelled
798
+ */
799
+ private beginColumnDrag;
800
+ /**
801
+ * true between the start and the end of a column resize or move by the user ({@link TableApi.isColumnDragging}).
802
+ *
803
+ * @returns {boolean} true while the user drags a column (or its resize handle)
804
+ * @since 1.1.110
805
+ */
806
+ isColumnDragging(): boolean;
807
+ /**
808
+ * Cancels a running column drag like Escape ({@link TableApi.cancelColumnDrag}): the old width resp. order is back,
809
+ * the end is reported with type `"cancel"`. Without a running drag nothing happens.
810
+ *
811
+ * @param {Event} [originalEvent] - the key event (Escape)
812
+ * @since 1.1.110
813
+ */
814
+ cancelColumnDrag(originalEvent?: Event): void;
815
+ /** Reports a column drag to the listener (and the subscribers). */
816
+ private emitColumnDragEvent;
789
817
  /**
790
818
  * Handles mouse dragging on the frame.
791
819
  *
@@ -793,6 +821,12 @@ export declare class TableScope extends RenderScope implements OnActionTriggered
793
821
  * @param startMouseEvent
794
822
  */
795
823
  mouseDraggingOnFrame(mouseEvent: GeMouseEvent, startMouseEvent: GeMouseEvent | undefined): void;
824
+ /**
825
+ * Moves the dragged column to the column under the mouse: within its area (fixed west, center, fixed east), locked
826
+ * columns (`movable: false`) keep their place; over a locked column it moves as far as it can towards it (the same
827
+ * result whether the mouse passed the columns in between in single frames or jumped there in one).
828
+ */
829
+ private moveDraggedColumn;
796
830
  /**
797
831
  * Handles the end of mouse dragging event on a frame.
798
832
  *
@@ -1028,13 +1062,47 @@ export declare class TableScope extends RenderScope implements OnActionTriggered
1028
1062
  * Calculates a width per column that fits the widest text of header, body and footer.
1029
1063
  *
1030
1064
  * The text is measured with the font and padding of a rendered cell label (canvas `measureText`),
1031
- * so dates, numbers and long names are no longer cut off. To stay fast with millions of rows, the body
1032
- * is scanned once by character length and only the longest candidates per column are measured.
1065
+ * so dates, numbers and long names are no longer cut off. The body is scanned once by character length and only the
1066
+ * longest candidates per column are measured. Body rows: all up to {@link ColumnAutoWidthOptionsIf.maxRows}
1067
+ * (10 000), above only the rows drawn now (since 1.1.110 – before, millions of rows froze the page).
1033
1068
  * Without a canvas (e.g. in tests) it falls back to 8 px per character.
1034
1069
  *
1035
1070
  * @returns {number[]} one width in px per column (at least 50 px)
1036
1071
  */
1037
1072
  calcAutoColumnWidths(): number[];
1073
+ /** The default widths of some columns: one pass over header, footer and the measured body rows. */
1074
+ private measureDefaultColumnWidths;
1075
+ /** The body rows the auto width measures: all up to `maxRows`, above only the rows drawn now. */
1076
+ private getMeasuredBodyRows;
1077
+ private getColumnAutoWidthOptions;
1078
+ /**
1079
+ * The fitted width of a column: {@link ColumnAutoWidthOptionsIf.getWidth} or the default, limited by
1080
+ * `minWidth` / `maxWidth`.
1081
+ */
1082
+ private calcAutoColumnWidth;
1083
+ /**
1084
+ * Fits the width of one column to its content ({@link TableApi.autoResizeColumn}). No event.
1085
+ *
1086
+ * @param {number | string | ColumnDefIf} column - index, property or column definition
1087
+ * @returns {number | undefined} the width set, `undefined` for an unknown column, a row number column or
1088
+ * `resizable: false`
1089
+ * @since 1.1.110
1090
+ */
1091
+ autoResizeColumn(column: number | string | ColumnDefIf): number | undefined;
1092
+ /**
1093
+ * A double click on the resize handle: fits the width (if allowed) and reports start and end with source
1094
+ * `"dblclick"`. Called by the {@link MouseHandler}.
1095
+ *
1096
+ * @param {number} columnIndex - the column of the handle
1097
+ * @param {Event} [originalEvent] - the double click
1098
+ * @returns {boolean} true if the width was fitted
1099
+ * @since 1.1.110
1100
+ */
1101
+ autoResizeColumnByUser(columnIndex: number, originalEvent?: Event): boolean;
1102
+ /** Not for a row number column (its width follows the numbers), `resizable: false` or a hidden column. */
1103
+ private isColumnAutoResizable;
1104
+ /** Index of a column given by index, property or column definition; -1 if unknown. */
1105
+ private resolveColumnIndex;
1038
1106
  setColumnWidth(columnIndex: number, width: number): void;
1039
1107
  /**
1040
1108
  * Shows or hides a column and repaints (see {@link TableApi.setColumnVisible}).
package/llms.txt CHANGED
@@ -401,7 +401,10 @@ listener.onModelChanged = (evt: GeModelChangeEvent) => {
401
401
  ### R12 – Columns: fixed, hidden, width, index
402
402
 
403
403
  - Fixed columns belong to the **model**: `TableFactory.createTableModel({ rows, properties, fixedLeftColumnCount: 1, fixedRightColumnCount: 1 })`.
404
- - `tableApi.setColumnVisible("total", false)`, `tableApi.setColumnWidth(index, px)`, `tableApi.autoResizeColumns()`.
404
+ - `tableApi.setColumnVisible("total", false)`, `tableApi.setColumnWidth(index, px)`, `tableApi.autoResizeColumns()`,
405
+ one column `tableApi.autoResizeColumn("name")` (since 1.1.110).
406
+ - Lock a column for the user: `ColumnDef.create({ …, resizable: false, movable: false })` (since 1.1.110). Save the
407
+ layout when the user drags: R18.
405
408
  - Row numbers (`rowNumbersVisible: true`), a checkbox column or a row-group column shift all column indices by one –
406
409
  always `tableApi.getColumnIndex(property)`. Header groups: `instructions.md` 5.2, demo "headergroup".
407
410
 
@@ -455,6 +458,27 @@ host without destroying the first.
455
458
  - **Drag between tables**: row drag works inside one table (`rowDrag: true`); between tables use `onRowDragEnd` +
456
459
  `rowDragOptions.managed: false` and move the data yourself.
457
460
 
461
+ ### R18 – Save the column layout when the user resizes or moves a column (since 1.1.110)
462
+
463
+ ```ts
464
+ import { EventAdapter } from "@guiexpert/table";
465
+ import type { GeColumnMoveEvent, GeColumnResizeEvent, TableApi } from "@guiexpert/table";
466
+
467
+ declare const tableApi: TableApi;
468
+ const listener = new EventAdapter(); // 5th argument of new TableScope(…); wrappers: columnResizeEnd …
469
+ listener.onColumnResizeEnd = (evt: GeColumnResizeEvent) => {
470
+ if (evt.type === "end") localStorage.setItem("layout", JSON.stringify(tableApi.getState(["columns"])));
471
+ };
472
+ listener.onColumnMoveEnd = (evt: GeColumnMoveEvent) => {
473
+ if (evt.moved) localStorage.setItem("layout", JSON.stringify(tableApi.getState(["columns"])));
474
+ };
475
+ ```
476
+
477
+ Once per drag, after the table applied the change (no `setTimeout`); `type: "cancel"` after Escape; API calls
478
+ (`setColumnWidth`, `applyState`, `autoResizeColumns`) report nothing. Double click on the resize handle fits the width
479
+ (`source: "dblclick"`; own measurement `tableOptions.columnAutoWidth.getWidth`). Renders of a live table while the
480
+ user drags are fine. → instructions.md 6.27
481
+
458
482
  More (short): tree + checkboxes `TreeFactory` (instructions.md 6.6), master/detail `masterDetail` (6.18), row grouping
459
483
  `RowGroupFactory` (5.6), pivot `PivotFactory` (5.7), calculated columns `valueGetter`/`formula` (5.2, 5.9), spreadsheet
460
484
  `SpreadsheetFactory.create` (6.25), cover grid (5.10), totals footer `footerAggregates` (5.4), row numbers (5.2).
@@ -498,6 +522,9 @@ Symptom → cause → fix.
498
522
  dedupe `@guiexpert/table`.
499
523
  - **TypeScript: unknown option** → "corrected" a misspelled API → use `showCheckboxWihoutExtraColumn`, `isHeaderVisibe`,
500
524
  `emmitDataKey`, `createCoverGrisDefaultOptions` as they are.
525
+ - **Own `mousedown`/`mouseup`/`dblclick` listeners on the header** (to save widths, lock a column, fit on double click)
526
+ → the table had no column events before 1.1.110 → `onColumnResizeEnd` / `onColumnMoveEnd` (R18),
527
+ `ColumnDef.resizable: false` / `movable: false`, the built-in double click (`columnAutoWidth`).
501
528
  - **`AsyncBodyAreaModelObjectArray` cannot be imported** → unfinished, not exported → own `AreaModel` (R8, R17).
502
529
  - **Invented APIs** – these do NOT exist: `getSelectedRows()`, `onKeyDown`, `api.addRow()`, `api.refresh()`,
503
530
  `setData()`, `gridOptions`, `rowData`, `columnDefs` as a table option (it is a model parameter). Use R4–R7.
@@ -511,6 +538,7 @@ Full answers: instructions.md section 6.26 (https://github.com/guiexperttable/mo
511
538
  - Rows that keep coming (log, stream, ticker of trades): `tableApi.appendRows(rows, { follow: "auto", maxRows })` (since 1.1.108) – in place, O(k), one render, filter/sort only for new rows, selection/focus stay; freeze = `follow: false`, jump to the end = `repaintHard({ py: Number.MAX_SAFE_INTEGER })`. Own AreaModel: `rowsAppended(count, { removedAtStart })`. Not `addRows()`.
512
539
  - `repaint()` = values changed, same row count; `repaintHard()` = row count or heights changed; `updateCells()` = single cells.
513
540
  - Variable row heights: implement `getRowHeight(i)`; `getConstantRowHeight()` only if all rows are equally high. `defaultRowHeights` only applies to models the factory builds or whose `getRowHeight()` returns 0.
541
+ - Save the column layout when the user resizes or moves a column: `onColumnResizeEnd` / `onColumnMoveEnd` (wrappers `columnResizeEnd`, `columnMoveEnd`, since 1.1.110) – once per drag, after the change; read `tableApi.getState(["columns"])` there. Lock a column: `ColumnDef.resizable: false`, `movable: false`. Double click on the column border fits the width (`columnAutoWidth`, `tableApi.autoResizeColumn(column)`).
514
542
  - Empty table: the host div needs a fixed height and the stylesheet `@guiexpert/table/css/main.css`.
515
543
 
516
544
  ## Feature reference
@@ -544,7 +572,8 @@ What the table can do – one line per feature, details in instructions.md (each
544
572
  - Undo/redo (since 1.1.99): tableOptions.undo: true | { maxSteps, maxCells, onConflict: "skip" | "overwrite", recordApiChanges } – Ctrl+Z / Ctrl+Y (Cmd+Shift+Z) for edit, fill, paste, cut, clear, batch edits and structure changes; rows found by object (sorting/filter safe). api.undo(), redo(), canUndo(), canRedo(), clearUndoHistory(), getUndoState(), batchUndoable(label, fn); event onUndoStackChanged (GeUndoEvent: canUndo, canRedo, undoLabel, redoLabel).
545
573
  - Cell formulas with A1 references (since 1.1.99): tableOptions.cellFormulas: true | { columns, functions } (ColumnDef.cellFormulas: false = free text) – texts with "=" in the data are formulas ("'=" = text): A1, $A$1, B2:D9, A:A, 70 Excel functions (FORMULA_FUNCTION_INFO; SUM, AVERAGE, IF, IFS, VLOOKUP, XLOOKUP, INDEX/MATCH, SUMIFS, COUNTIFS, TEXT, DATE, EOMONTH …), errors #DIV/0! #REF! #NAME? #VALUE! #N/A #NUM! #CIRC!; dependency graph, only dependents recalculate; own parser, no eval. Formula input: colored references, pointing with arrows/mouse, F4 toggles $, function list (combobox) and hint. Fill/copy shift relative references; Excel export with real formulas: downloadExcel(name, author, { cellFormulas: true }). api.getCellInput/getCellValue/setCellInput/getCellAddress/getCellByAddress/evaluateFormula/getPrecedents/getDependents/recalculate/getSheetEngine. DOM-free: SheetEngine.fromArrays(data), shiftFormula, adjustFormula.
546
574
  - Spreadsheet mode (since 1.1.99): SpreadsheetFactory.create({ data: unknown[][], rowCount (100), columnCount (26), columnWidth, rowHeight, rowNumbers, locale, functions, tableOptions }) → { tableModel: SpreadsheetTableModel, tableOptions } – column letters (AreaModelSheetHeader), row numbers, sparse AreaModelSheet; preset tableOptions.spreadsheet: true | { editing, clipboard, undo, cellFormulas, fillHandle } (false = off) for any flat table (TableFactory columnLetters: true). FormulaBar(container, api) – address box + formula input. api.getActiveCell(), goToAddress("B2:C5"), insertRows(at, count), deleteRows, insertColumns, deleteColumns (references adjusted, one undo step, source "structure"), getSheetData() (inputs for JSON).
547
- - Angular signal component (since 1.1.100, Angular 22 and later): TableSignalComponent, selector guiexpert-table-signal, from @guiexpert/angular-table@22.x – inputs (input()) tableModel (TableModelIf | undefined), tableOptions (default new TableOptions()), loading, debounceMouseClickDelay (150, 0 = synchronous); tableApi = model<TableApi | undefined>() → [(tableApi)]="api" with api = signal<TableApi | undefined>(undefined), (tableApiChange), viewChild(TableSignalComponent)?.tableApi(); set when the table is built, switches directly to the new API on a new model/options, undefined after destroy, written from outside = replaced. Outputs (output()): no tableReady (the API comes as the signal tableApi / tableApiChange), mouseClicked, mouseMoved, mouseDragging, mouseDraggingEnded, contextMenu (camelCase – TableComponent's (contextmenu) also receives the native MouseEvent first), scroll, modelChanged, selectionChanged, focusChanged, checkboxChanged, stateChanged, rowDragStart/Move/End, batchEditChanged, detailExpandedChanged, editingChanged, undoStackChanged – events of the table (mouse, keyboard, scroll …) are emitted outside the Angular zone (write signals in handlers). effect() instead of ngOnChanges, no RxJS, zoneless (provideZonelessChangeDetection()), SSR: no table on the server. TableComponent (<guiexpert-table>) unchanged for Angular 14 – 22+.
575
+ - Angular signal component (since 1.1.100, Angular 22 and later): TableSignalComponent, selector guiexpert-table-signal, from @guiexpert/angular-table@22.x – inputs (input()) tableModel (TableModelIf | undefined), tableOptions (default new TableOptions()), loading, debounceMouseClickDelay (150, 0 = synchronous); tableApi = model<TableApi | undefined>() → [(tableApi)]="api" with api = signal<TableApi | undefined>(undefined), (tableApiChange), viewChild(TableSignalComponent)?.tableApi(); set when the table is built, switches directly to the new API on a new model/options, undefined after destroy, written from outside = replaced. Outputs (output()): no tableReady (the API comes as the signal tableApi / tableApiChange), mouseClicked, mouseMoved, mouseDragging, mouseDraggingEnded, contextMenu (camelCase – TableComponent's (contextmenu) also receives the native MouseEvent first), scroll, modelChanged, selectionChanged, focusChanged, checkboxChanged, stateChanged, rowDragStart/Move/End, batchEditChanged, detailExpandedChanged, editingChanged, undoStackChanged, columnResizeStart/End, columnMoveStart/End (since 1.1.110) – events of the table (mouse, keyboard, scroll …) are emitted outside the Angular zone (write signals in handlers). effect() instead of ngOnChanges, no RxJS, zoneless (provideZonelessChangeDetection()), SSR: no table on the server. TableComponent (<guiexpert-table>) unchanged for Angular 14 – 22+.
576
+ - Column drag (since 1.1.110): events EventListenerIf.onColumnResizeStart/End (GeColumnResizeEvent: type "start" | "end" | "cancel", source "mouse" | "dblclick", columnIndex, columnDef, property, oldWidth, newWidth) and onColumnMoveStart/End (GeColumnMoveEvent: type, fromIndex, toIndex, columnDef, property, columnOrder, moved) – only user actions, once per drag, the end after the change, start after 4 px, Escape / tableApi.cancelColumnDrag() = "cancel", tableApi.isColumnDragging(); wrappers columnResizeStart/End, columnMoveStart/End. Locks: ColumnDef.resizable: false (no handle, no double click, no auto width), movable: false (fixed place, applyState keeps it); RowNumberColumnDef has both false; a column stays in its area (west/center/east). Double click on the resize handle fits the width (no sorting): tableOptions.columnAutoWidth: { doubleClick (true), maxRows (10,000: all body rows, above only the rendered rows), getWidth(ctx: ColumnAutoWidthContextIf { columnIndex, columnDef, property, source, firstRenderedRow, lastRenderedRow, measureText(text), getDefaultWidth() }) => number | undefined }, limited by minWidth/maxWidth; tableApi.autoResizeColumn(index | property | ColumnDef) → width | undefined.
548
577
  - Live data / log tail (since 1.1.108): tableApi.appendRows(rows, { follow?: "auto" (default: stays at the end if it was at the end, else the first visible row stays) | true (always the end) | false (freeze), maxRows?: ring buffer – oldest rows removed from the start in blocks of ≥ 1 % }) → AppendRowsResultIf { added, shown, removed, following } | false (tree, row groups, pivot, spreadsheet, cell formulas) – AreaModelObjectArray body only, IN PLACE (pushes to your array, no copy), O(k): externalFilterFunction only for the new rows, sorted column / sort(fn) → merged in (binary search, behind equal rows); selection, focus, Shift+arrow anchor and checked rows stay at their rows (RowIndexShift, SelectionModel/FocusModel.adjustForRowChanges – no onSelectionChanged unless a selected row left the buffer); one render pass, none when only rows below the view came in (cells not rebuilt). Held mouse button: "auto" keeps the rows under the pointer and follows again after release. Own AreaModel: append yourself, then tableApi.rowsAppended(count, { follow, removedAtStart }). Not addRows() (copies all rows, resets filter and sort). Showcase logtail, demo apps/table-demo/log-tail.html, LogTailController + createLogTailModelAndOptions in @guiexpert/demo-table-models.
549
578
  - Custom filtering
550
579
  - In-place cell editing
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guiexpert/table",
3
- "version": "1.1.109",
3
+ "version": "1.1.110",
4
4
  "license": "MIT",
5
5
  "author": "Dr. Marc Kronberg <info@gui.expert> (https://gui.expert)",
6
6
  "repository": {