@guiexpert/table 1.1.92 → 1.1.94

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.
Files changed (68) hide show
  1. package/README.md +13 -0
  2. package/css/main.css +142 -0
  3. package/css/table-classes.css +34 -0
  4. package/css/table-color-vars.css +32 -0
  5. package/css/table-pivot.css +42 -0
  6. package/css/table-row-group.css +25 -0
  7. package/css/table-tree.css +9 -0
  8. package/index.cjs +44 -36
  9. package/index.d.ts +39 -0
  10. package/index.js +6247 -3509
  11. package/lib/table/aggregation/aggregate-internals.d.ts +30 -0
  12. package/lib/table/aggregation/aggregate-spec.if.d.ts +51 -0
  13. package/lib/table/aggregation/aggregate.d.ts +58 -0
  14. package/lib/table/aggregation/aggregator-name.type.d.ts +39 -0
  15. package/lib/table/aggregation/aggregator.if.d.ts +51 -0
  16. package/lib/table/aggregation/aggregators.d.ts +37 -0
  17. package/lib/table/aggregation/create-value-getter.d.ts +18 -0
  18. package/lib/table/aggregation/selection-aggregates.if.d.ts +35 -0
  19. package/lib/table/aggregation/value-accessor.type.d.ts +13 -0
  20. package/lib/table/data/common/row-number-column-def.d.ts +107 -0
  21. package/lib/table/data/common/row-number-columns.d.ts +17 -0
  22. package/lib/table/data/common/row-number-options.d.ts +19 -0
  23. package/lib/table/data/common/row-number-options.if.d.ts +44 -0
  24. package/lib/table/data/tablemodel/areamodel/aggregate-footer-options.if.d.ts +73 -0
  25. package/lib/table/data/tablemodel/areamodel/area-model-aggregate-footer.d.ts +145 -0
  26. package/lib/table/data/tablemodel/areamodel/area-model-cell-groups.d.ts +18 -1
  27. package/lib/table/data/tablemodel/areamodel/area-model-pivot-footer.d.ts +47 -0
  28. package/lib/table/data/tablemodel/areamodel/area-model-pivot.d.ts +250 -0
  29. package/lib/table/data/tablemodel/areamodel/area-model-row-groups.d.ts +314 -0
  30. package/lib/table/data/tablemodel/areamodel/area-model-tree.d.ts +14 -0
  31. package/lib/table/data/tablemodel/areamodel/area-model.if.d.ts +39 -0
  32. package/lib/table/data/tablemodel/areamodel/derived-values-change.if.d.ts +30 -0
  33. package/lib/table/factory/pivot-factory.d.ts +63 -0
  34. package/lib/table/factory/pivot-table-model-para.if.d.ts +44 -0
  35. package/lib/table/factory/row-group-factory.d.ts +70 -0
  36. package/lib/table/factory/row-group-table-model-para.if.d.ts +66 -0
  37. package/lib/table/factory/table-factory.d.ts +45 -1
  38. package/lib/table/instanceof-workaround.d.ts +143 -6
  39. package/lib/table/pivot/area-model-pivot-header.d.ts +9 -0
  40. package/lib/table/pivot/pivot-column-def.d.ts +28 -0
  41. package/lib/table/pivot/pivot-column-info.if.d.ts +25 -0
  42. package/lib/table/pivot/pivot-config.if.d.ts +58 -0
  43. package/lib/table/pivot/pivot-internals.d.ts +13 -0
  44. package/lib/table/pivot/pivot-layout.d.ts +40 -0
  45. package/lib/table/pivot/pivot-result.d.ts +157 -0
  46. package/lib/table/pivot/pivot-table-model.d.ts +86 -0
  47. package/lib/table/pivot/pivot-value.type.d.ts +67 -0
  48. package/lib/table/render-scope.d.ts +61 -4
  49. package/lib/table/renderer/aggregate-cell-renderer.d.ts +27 -0
  50. package/lib/table/renderer/row-number-cell-renderer.d.ts +27 -0
  51. package/lib/table/rowgroup/group-rows-internals.d.ts +14 -0
  52. package/lib/table/rowgroup/group-rows-options.if.d.ts +13 -0
  53. package/lib/table/rowgroup/group-rows.d.ts +48 -0
  54. package/lib/table/rowgroup/row-group-column-def.d.ts +26 -0
  55. package/lib/table/rowgroup/row-group-column-options.if.d.ts +27 -0
  56. package/lib/table/rowgroup/row-group-detail-row.d.ts +8 -0
  57. package/lib/table/rowgroup/row-group-field.type.d.ts +56 -0
  58. package/lib/table/rowgroup/row-group-internals.d.ts +31 -0
  59. package/lib/table/rowgroup/row-group-layout.d.ts +13 -0
  60. package/lib/table/rowgroup/row-group-model-options.if.d.ts +39 -0
  61. package/lib/table/rowgroup/row-group.d.ts +51 -0
  62. package/lib/table/rowgroup/row-group.if.d.ts +48 -0
  63. package/lib/table/selection/selection-aggregator.d.ts +37 -0
  64. package/lib/table/selection/selection-service.d.ts +7 -0
  65. package/lib/table/service/convenience-dom.service.d.ts +5 -0
  66. package/lib/table/table-api.d.ts +112 -1
  67. package/lib/table/table-scope.d.ts +19 -0
  68. package/package.json +3 -2
@@ -0,0 +1,314 @@
1
+ import { AreaModelTree } from './area-model-tree.js';
2
+ import { ColumnDefIf } from '../column/column-def.if.js';
3
+ import { CellRendererIf } from '../../../renderer/cell-render.if.js';
4
+ import { FilterFunction } from '../../common/filter-function.js';
5
+ import { SortItem } from '../../common/sort-item.js';
6
+ import { DerivedValuesChangeIf } from './derived-values-change.if.js';
7
+ import { RowGroupField } from '../../../rowgroup/row-group-field.type.js';
8
+ import { RowGroupModelOptionsIf } from '../../../rowgroup/row-group-model-options.if.js';
9
+ import { RowGroup } from '../../../rowgroup/row-group.js';
10
+
11
+ /**
12
+ * The body of a row grouping: flat rows grouped by one or more levels, each group a row with an arrow, a label
13
+ * (`"Nord (3)"`) and aggregates per column (sum, average, …); below it the sub-groups or the original rows. Usually
14
+ * created by {@link RowGroupFactory.createTableModel}, which also adds the group column, hides the grouped columns and
15
+ * adds a total row.
16
+ *
17
+ * It is a tree ({@link AreaModelTree}, `isAreaModelTree` is true): group rows are {@link TreeRowIf tree rows} with a
18
+ * {@link RowGroupIf} as `data`, detail rows hold your objects. Expanding, `tableApi.expandAll()`, keyboard, screen
19
+ * reader attributes and remembering expanded groups (`autoRestoreOptions`) work as in a tree table.
20
+ *
21
+ * - **Filter**: `externalFilterFunction` gets your object (as in a flat table) – never a group. Groups without a match
22
+ * disappear; aggregates and counts follow the filter.
23
+ * - **Sorting**: by an aggregated column → groups by aggregate, rows by value; by the group column → groups by key;
24
+ * `tableApi.sort(fn)` sorts the rows inside their groups with `fn(a, b)` on your objects.
25
+ * - **Editing**: group rows are not editable. A changed value recomputes only the groups of that row; a changed
26
+ * grouped value moves the row to its new group.
27
+ * - **Regrouping**: {@link TableApi.setRowGroupBy} (or {@link setGroupBy} + `tableApi.repaintHard()`) – expanded
28
+ * groups, filter, sorting and checked rows stay.
29
+ * - Aggregates are computed when a group row is read after a change – once, not per repaint or scroll frame.
30
+ *
31
+ * @example
32
+ * const tableModel = RowGroupFactory.createTableModel<Sale>({
33
+ * rows: sales, columnDefs, groupBy: ["region", "produkt"], aggregations: { umsatz: "sum" }
34
+ * });
35
+ * const body = tableModel.getBodyModel() as AreaModelRowGroups<Sale>;
36
+ * body.getGroups().map(g => g.results.umsatz); // [270, 180]
37
+ *
38
+ * @see Training: [Row grouping](https://gui.expert/training/row-grouping/)
39
+ *
40
+ * @typeParam T - the row type
41
+ * @since 1.1.94
42
+ * @category Row grouping
43
+ */
44
+ export declare class AreaModelRowGroups<T = any> extends AreaModelTree<any> {
45
+ /** Marker for {@link isAreaModelRowGroups} (works across copies of the core). `type` stays `"AreaModelTree"`. */
46
+ readonly kind = "AreaModelRowGroups";
47
+ /** The options (groupBy as set by {@link setGroupBy}). */
48
+ readonly options: RowGroupModelOptionsIf<T>;
49
+ /** Properties of the columns the factory hid for the current grouping. @internal */
50
+ hiddenColumnProperties: string[];
51
+ private sourceRows;
52
+ private fields;
53
+ private groups;
54
+ private readonly specs;
55
+ private readonly specByKey;
56
+ private readonly textRenderer;
57
+ /** the tree row of every group by id */
58
+ private groupRowById;
59
+ /** the tree row of every source row by source index */
60
+ private detailRows;
61
+ /** the filter of the user (gets the objects) and its result per source index */
62
+ private userFilter?;
63
+ private accepted?;
64
+ private allDirty;
65
+ private readonly dirtyLeaves;
66
+ private totals;
67
+ private lastSort?;
68
+ private batchDepth;
69
+ private regroupPending;
70
+ private builds;
71
+ /**
72
+ * @param {T[]} rows - the rows (not changed by the model)
73
+ * @param {ColumnDefIf[]} columnDefs - the columns of the table, usually with a {@link RowGroupColumnDef}
74
+ * @param {RowGroupModelOptionsIf<T>} options - levels, aggregations, expanded state …
75
+ * @param {number} [defaultRowHeight=34] - row height in px
76
+ */
77
+ constructor(rows: T[], columnDefs: ColumnDefIf[], options: RowGroupModelOptionsIf<T>, defaultRowHeight?: number);
78
+ /**
79
+ * The rows the groups are built from.
80
+ *
81
+ * @returns {T[]} the source rows (do not modify the array – use {@link setRows})
82
+ *
83
+ * @example
84
+ * const all = body.getSourceRows().length;
85
+ */
86
+ getSourceRows(): T[];
87
+ /**
88
+ * Replaces the rows and groups them again (expanded groups, filter and sorting stay). Repaint with
89
+ * `tableApi.repaintHard()` – the row count changes.
90
+ *
91
+ * @param {T[]} rows - the new rows
92
+ *
93
+ * @example
94
+ * body.setRows(await loadSales());
95
+ * tableApi.repaintHard();
96
+ */
97
+ setRows(rows: T[]): void;
98
+ /**
99
+ * The current levels.
100
+ *
101
+ * @returns {RowGroupField<T>[]} a copy of the levels
102
+ *
103
+ * @example
104
+ * body.getGroupBy(); // ["region", "produkt"]
105
+ */
106
+ getGroupBy(): RowGroupField<T>[];
107
+ /**
108
+ * Groups the rows by other levels. Expanded groups whose path stays keep their state; filter, sorting and checked
109
+ * rows stay. An empty list shows the rows without groups. Prefer {@link TableApi.setRowGroupBy}: it also shows and
110
+ * hides the grouped columns and repaints.
111
+ *
112
+ * @param {RowGroupField<T>[]} groupBy - the new levels, top first
113
+ *
114
+ * @example
115
+ * body.setGroupBy(["produkt"]);
116
+ * tableApi.repaintHard();
117
+ */
118
+ setGroupBy(groupBy: RowGroupField<T>[]): void;
119
+ /**
120
+ * Builds the groups again from the current rows – after grouped values were changed outside the table (inside the
121
+ * table, editing a grouped value moves the row by itself). Repaint with `tableApi.repaintHard()`.
122
+ *
123
+ * @example
124
+ * sales[0].region = "Süd"; // a live update of a grouped value
125
+ * body.regroup();
126
+ * tableApi.repaintHard();
127
+ */
128
+ regroup(): void;
129
+ /**
130
+ * The groups of the top level (with sub-groups, counts and aggregates).
131
+ *
132
+ * @returns {RowGroup<T>[]} the groups in display order (do not modify)
133
+ *
134
+ * @example
135
+ * body.getGroups().map(g => `${g.label}: ${g.results.umsatz}`); // ["Nord: 270", "Süd: 180"]
136
+ */
137
+ getGroups(): RowGroup<T>[];
138
+ /**
139
+ * The group with the given keys.
140
+ *
141
+ * @param {unknown[]} path - the keys from the top level down, e.g. `["Nord", "A"]`
142
+ * @returns {RowGroup<T> | undefined} the group, undefined if there is none
143
+ *
144
+ * @example
145
+ * body.findGroup(["Nord", "A"])?.results.umsatz; // 220
146
+ */
147
+ findGroup(path: unknown[]): RowGroup<T> | undefined;
148
+ /**
149
+ * The group of a displayed row.
150
+ *
151
+ * @param {number} rowIndex - index of the displayed body row
152
+ * @returns {RowGroup<T> | undefined} the group of a group row, undefined for a detail row
153
+ *
154
+ * @example
155
+ * const group = body.getRowGroup(0); // the first group (if the first row is a group row)
156
+ */
157
+ getRowGroup(rowIndex: number): RowGroup<T> | undefined;
158
+ /**
159
+ * Your object of a displayed detail row.
160
+ *
161
+ * @param {number} rowIndex - index of the displayed body row
162
+ * @returns {T | undefined} the object, undefined for a group row
163
+ *
164
+ * @example
165
+ * const sale = body.getDetailRow(1);
166
+ */
167
+ getDetailRow(rowIndex: number): T | undefined;
168
+ /**
169
+ * True if the displayed row is a group row.
170
+ *
171
+ * @param {number} rowIndex - index of the displayed body row
172
+ * @returns {boolean} true for a group row
173
+ *
174
+ * @example
175
+ * const bold = body.isGroupRowIndex(rowIndex);
176
+ */
177
+ isGroupRowIndex(rowIndex: number): boolean;
178
+ /**
179
+ * The aggregates over all rows the filter keeps (the same as the total row).
180
+ *
181
+ * @returns {Record<string, unknown>} key → result
182
+ *
183
+ * @example
184
+ * body.getTotals().umsatz; // 450
185
+ */
186
+ getTotals(): Record<string, unknown>;
187
+ /**
188
+ * The checked objects (detail rows) – with a checkbox column or `showCheckboxWihoutExtraColumn`.
189
+ *
190
+ * @returns {T[]} the checked objects in source order
191
+ *
192
+ * @example
193
+ * const selected = body.getCheckedRows();
194
+ */
195
+ getCheckedRows(): T[];
196
+ /**
197
+ * Expands the groups above a level and collapses the others: `0` collapses all, `1` opens the top level, a large
198
+ * number opens all. Repaint with `tableApi.repaintHard()`.
199
+ *
200
+ * @param {number} level - the number of open levels
201
+ *
202
+ * @example
203
+ * body.expandToLevel(1);
204
+ * tableApi.repaintHard();
205
+ */
206
+ expandToLevel(level: number): void;
207
+ /**
208
+ * Counts how often the groups were built (start, {@link setRows}, {@link setGroupBy}, {@link regroup}, an edited
209
+ * grouped value). The table compares it before and after an edit to know that the rows moved (→ hard repaint).
210
+ *
211
+ * @returns {number} the number of builds
212
+ *
213
+ * @example
214
+ * const before = body.getBuildCount();
215
+ * body.setValue(1, 1, "Ost");
216
+ * const moved = body.getBuildCount() !== before; // true: "region" is grouped
217
+ */
218
+ getBuildCount(): number;
219
+ /**
220
+ * Runs several `setValue` calls as one change: a row whose grouped value changes moves only at the end, so the row
221
+ * indices of the other calls stay valid (`tableApi.updateCells()` uses it).
222
+ *
223
+ * @param {() => void} fn - the changes
224
+ * @returns {boolean} true if the groups were built again at the end
225
+ *
226
+ * @example
227
+ * const moved = body.batch(() => {
228
+ * body.setValue(1, 1, "Süd"); // region – moves the row
229
+ * body.setValue(2, 4, 999); // still the row that was at index 2
230
+ * });
231
+ */
232
+ batch(fn: () => void): boolean;
233
+ getValueAt(rowIndex: number, columnIndex: number): any;
234
+ getTextValueAt(rowIndex: number, columnIndex: number): string;
235
+ /** Group rows: the {@link AggregateCellRenderer} for aggregates, no renderer otherwise; detail rows: the column's. */
236
+ getCellRenderer(rowIndex: number, columnIndex: number): CellRendererIf | undefined;
237
+ getCustomClassesAt(rowIndex: number, columnIndex: number): string[];
238
+ /**
239
+ * True for the label cell of a group row (the table gives it `role="rowheader"`).
240
+ *
241
+ * @param {number} rowIndex - index of the displayed body row
242
+ * @param {number} columnIndex - column index
243
+ * @returns {boolean} true for the group label
244
+ */
245
+ isGroupLabelCell(rowIndex: number, columnIndex: number): boolean;
246
+ /** Group rows and the group column are not editable. */
247
+ isEditable(rowIndex: number, columnIndex: number): boolean;
248
+ /**
249
+ * Sets a value of a detail row (group rows: `false`). Only the groups of that row are computed again; if a grouped
250
+ * value changed, the row moves to its new group (the row count may change).
251
+ */
252
+ setValue(rowIndex: number, columnIndex: number, value: any): boolean;
253
+ /**
254
+ * Filters the rows: `predictFn` gets **your objects** (as in a flat table), index and array of the source rows –
255
+ * never a group. Groups without a matching row disappear (also below collapsed groups); counts and aggregates follow
256
+ * the filter. Usually called by `tableApi.externalFilterChanged()` with `tableOptions.externalFilterFunction`.
257
+ *
258
+ * @param {FilterFunction<S>} predictFn - returns a truthy value for the rows to show
259
+ *
260
+ * @example
261
+ * tableOptions.externalFilterFunction = (sale: Sale) => sale.monat === "Jan";
262
+ * tableApi.externalFilterChanged(); // Nord (2) 150 · Süd (1) 80
263
+ */
264
+ externalFilterChanged<S>(predictFn: FilterFunction<S>): void;
265
+ /**
266
+ * Sorts groups and rows (see the class description); the order is applied again after regrouping.
267
+ *
268
+ * @param {SortItem[]} sortItems - primary key first
269
+ * @returns {boolean} false for an empty list
270
+ *
271
+ * @example
272
+ * body.doSort([new SortItem(tableApi.getColumnIndex("umsatz"), "desc")]); // groups by sum, rows by value
273
+ * tableApi.repaint();
274
+ */
275
+ doSort(sortItems: SortItem[]): boolean;
276
+ /**
277
+ * Sorts the rows inside their groups with your function – it gets your objects, as in a flat table. The groups keep
278
+ * their order. Used by `tableApi.sort(fn)`.
279
+ *
280
+ * @param {(a: any, b: any) => number} compareFn - compares two of your objects
281
+ *
282
+ * @example
283
+ * tableApi.sort((a: Sale, b: Sale) => b.umsatz - a.umsatz);
284
+ */
285
+ sort(compareFn: (a: any, b: any) => number): void;
286
+ /**
287
+ * Drops the computed aggregates; with `change` (an edit or `updateCells`) only those of the groups of the changed rows.
288
+ * The table calls it; the next read computes them again.
289
+ *
290
+ * @param {DerivedValuesChangeIf} [change] - the changed rows
291
+ *
292
+ * @example
293
+ * sales[0].umsatz = 999; // changed outside the table
294
+ * tableApi.repaint(); // calls invalidateDerivedValues() – all groups are computed again
295
+ */
296
+ invalidateDerivedValues(change?: DerivedValuesChangeIf): void;
297
+ /** After sorting the tree rows: the groups (`getGroups()`, `children`) in display order. */
298
+ private syncGroupOrder;
299
+ /** The label of a group row in the group column. */
300
+ protected getGroupLabel(group: RowGroup<T>): string;
301
+ /** Computes what is missing: everything after a change of data/filter/grouping, otherwise the dirty paths. */
302
+ protected ensureAggregates(): void;
303
+ private markRowDirty;
304
+ private keysOf;
305
+ /** A comparator of two sibling rows for one sort column. */
306
+ private createRowComparator;
307
+ /**
308
+ * (Re)builds groups and tree rows from the source rows, keeps the expanded state of known groups, the checked
309
+ * detail rows, the filter and the sorting.
310
+ */
311
+ private build;
312
+ private createGroupRow;
313
+ private isExpandedByDefault;
314
+ }
@@ -106,6 +106,20 @@ export declare class AreaModelTree<S> extends AbstractAreaModel<TreeRow<S>> {
106
106
  * @since 1.1.91
107
107
  */
108
108
  getDisplayedChildren(row: TreeRowIf<S>): TreeRowIf<S>[];
109
+ /**
110
+ * The leaves (rows without children) of the whole tree, depth-first in the current order – independent of the
111
+ * expanded state. Used for totals: collapsing a node does not change them, and parent rows are not counted twice.
112
+ *
113
+ * @param {boolean} [filtered=true] - only the leaves the active filter keeps (`externalFilterChanged`); `false`: all
114
+ * @returns {TreeRowIf<S>[]} a new array
115
+ *
116
+ * @example
117
+ * const treeModel = tableModel.getBodyModel() as AreaModelTree<OrderNode>;
118
+ * const total = aggregate(treeModel.getLeafRows(), row => row.data.total, "sum");
119
+ *
120
+ * @since 1.1.94
121
+ */
122
+ getLeafRows(filtered?: boolean): TreeRowIf<S>[];
109
123
  /**
110
124
  * The displayed rows: all parents expanded and accepted by the active filter, in display order.
111
125
  *
@@ -1,3 +1,4 @@
1
+ import { DerivedValuesChangeIf } from './derived-values-change.if.js';
1
2
  import { CellRendererIf } from '../../../renderer/cell-render.if.js';
2
3
  import { CheckedType } from '../../common/checked-type.js';
3
4
  import { CheckboxModelIf } from '../../../checkbox/checkbox-model.if.js';
@@ -280,6 +281,44 @@ export interface AreaModelIf {
280
281
  * @since 1.1.92
281
282
  */
282
283
  getUnfilteredRowCount?(): number;
284
+ /**
285
+ * Optional: drops cached, derived values (e.g. the totals of an {@link AreaModelAggregateFooter}). The table calls
286
+ * it for all three areas when data may have changed: filter, sorting, editing, `updateCells()` and the public
287
+ * `tableApi.repaint()` / `repaintHard()` – not on scrolling or internal repaints.
288
+ *
289
+ * After an edit and `updateCells()` the table passes `change` (since 1.1.94): the area and the displayed rows that
290
+ * changed – a model may recompute only what depends on them ({@link AreaModelRowGroups} recomputes the groups of
291
+ * these rows). Without `change` anything may have changed.
292
+ *
293
+ * @example
294
+ * class CachedTotalsFooter extends AreaModel {
295
+ * private total?: number;
296
+ * constructor(private readonly body: AreaModelObjectArray<Order>) {
297
+ * super();
298
+ * this.areaIdent = "footer";
299
+ * }
300
+ * invalidateDerivedValues(): void { this.total = undefined; }
301
+ * override getRowCount(): number { return 1; }
302
+ * override getValueAt(_row: number, _col: number): any {
303
+ * return this.total ??= this.body.getFilteredRows().reduce((sum, o) => sum + o.total, 0);
304
+ * }
305
+ * }
306
+ *
307
+ * @since 1.1.94
308
+ */
309
+ invalidateDerivedValues?(change?: DerivedValuesChangeIf): void;
310
+ /**
311
+ * Optional: true for a cell that names its row (a label such as "Nord" or "Total") – the table gives it
312
+ * `role="rowheader"` instead of `gridcell`, so screen readers announce it with the row.
313
+ *
314
+ * @example
315
+ * class LabeledBody extends AreaModel {
316
+ * isRowHeaderCell(_rowIndex: number, columnIndex: number): boolean { return columnIndex === 0; }
317
+ * }
318
+ *
319
+ * @since 1.1.94
320
+ */
321
+ isRowHeaderCell?(rowIndex: number, columnIndex: number): boolean;
283
322
  /**
284
323
  * Returns the value of the cell at the specified row and column indices.
285
324
  * This is one of the most important methods in the area model, as it provides
@@ -0,0 +1,30 @@
1
+ import { AreaIdent } from '../area-ident.type.js';
2
+
3
+ /**
4
+ * What changed when the table calls {@link AreaModelIf.invalidateDerivedValues} after an edit or
5
+ * {@link TableApi.updateCells}: the area and the displayed rows. A model may use it to recompute only what depends on
6
+ * these rows (a row grouping recomputes the path of the changed row only). Without it everything may have changed.
7
+ *
8
+ * @example
9
+ * class MyModel extends AreaModel {
10
+ * private readonly dirtyRows = new Set<number>();
11
+ * private allDirty = true;
12
+ *
13
+ * invalidateDerivedValues(change?: DerivedValuesChangeIf): void {
14
+ * if (change?.area === "body") {
15
+ * change.rowIndices.forEach(r => this.dirtyRows.add(r)); // only these rows
16
+ * } else {
17
+ * this.allDirty = true; // anything may have changed
18
+ * }
19
+ * }
20
+ * }
21
+ *
22
+ * @since 1.1.94
23
+ * @category Area models
24
+ */
25
+ export interface DerivedValuesChangeIf {
26
+ /** The area whose values changed. */
27
+ area: AreaIdent;
28
+ /** The changed rows (indices of the displayed rows at the time of the change). */
29
+ rowIndices: number[];
30
+ }
@@ -0,0 +1,63 @@
1
+ import { PivotTableModelParaIf } from './pivot-table-model-para.if.js';
2
+ import { PivotTableModel } from '../pivot/pivot-table-model.js';
3
+
4
+ /**
5
+ * Creates pivot tables: rows grouped by row fields (a hierarchy with subtotals), the values of column fields as
6
+ * columns (several fields = a multi-row header), aggregated values in the cells, total column and total row.
7
+ * MIT-licensed, no dependencies – at ag-Grid this is an Enterprise feature.
8
+ *
9
+ * The configuration has the form of a row grouping ({@link RowGroupFactory}): fields are property paths, functions
10
+ * (e.g. the quarter of a date) or {@link RowGroupFieldIf} with label, format and order; values use the aggregators of
11
+ * {@link Aggregators} (`sum`, `avg`, `count` …) or your own {@link AggregatorIf}. Subtotals and totals are merged from
12
+ * aggregation states – an average is always the average of the rows.
13
+ *
14
+ * Filter with `externalFilterFunction` (it gets your source objects, before the aggregation), sort by clicking a value
15
+ * column, export with `downloadExcel()`, and pivot again at runtime with {@link TableApi.setPivotConfig}.
16
+ *
17
+ * @example
18
+ * // case 1: regions × months, sum of umsatz, total column and total row
19
+ * const tableModel = PivotFactory.createTableModel<Sale>({
20
+ * rows: sales,
21
+ * rowFields: ["region"],
22
+ * columnFields: [{ value: "monat", order: "insertion" }],
23
+ * values: ["umsatz"]
24
+ * });
25
+ * // Region | Jan | Feb | Total
26
+ * // Nord | 150 | 120 | 270
27
+ * // Süd | 80 | 100 | 180
28
+ * // Total | 230 | 220 | 450
29
+ *
30
+ * @example
31
+ * // countries › cities × years, revenue, all groups closed; pivot by another field with a select
32
+ * const tableModel = PivotFactory.createTableModel<Order>({
33
+ * rows: orders,
34
+ * rowFields: ["country", "city"],
35
+ * columnFields: [{ name: "year", label: "Year", value: o => new Date(o.date).getFullYear() }],
36
+ * values: [{ value: "total", label: "Revenue" }],
37
+ * expanded: 0
38
+ * });
39
+ * select.addEventListener("change", () => tableApi.setPivotConfig({ columnFields: [select.value] }));
40
+ *
41
+ * @see Training: [Pivot tables](https://gui.expert/training/pivot-tables/)
42
+ *
43
+ * @since 1.1.94
44
+ * @category Pivot
45
+ */
46
+ export declare class PivotFactory {
47
+ /**
48
+ * Creates the table model of a pivot table (see the class description).
49
+ *
50
+ * @param {PivotTableModelParaIf<T>} p - rows, row fields, column fields, values, options
51
+ * @returns {PivotTableModel<T>} the table model – a {@link TableModelIf}
52
+ *
53
+ * @example
54
+ * // case 3: the average – Jan total is 230 / 3 = 76.67, not the mean of the region averages (77.5)
55
+ * const tableModel = PivotFactory.createTableModel<Sale>({
56
+ * rows: sales, rowFields: ["region"], columnFields: ["monat"],
57
+ * values: [{ value: "umsatz", aggregator: "avg" }]
58
+ * });
59
+ */
60
+ static createTableModel<T = any>(p: PivotTableModelParaIf<T>): PivotTableModel<T>;
61
+ /** The pivot part of the parameters. */
62
+ private static toConfig;
63
+ }
@@ -0,0 +1,44 @@
1
+ import { TableOptionsIf } from '../data/options/table-options.if.js';
2
+ import { DefaultRowHeightsIf } from '../data/options/default-row-heights.if.js';
3
+ import { GetT } from '../data/common/get-t.js';
4
+ import { SelectionModelIf } from '../selection/selection-model.if.js';
5
+ import { RowNumberOptionsIf } from '../data/common/row-number-options.if.js';
6
+ import { SizeIf } from '../data/common/size.if.js';
7
+ import { RowGroupColumnOptionsIf } from '../rowgroup/row-group-column-options.if.js';
8
+ import { PivotConfigIf } from '../pivot/pivot-config.if.js';
9
+
10
+ /**
11
+ * Parameters of {@link PivotFactory.createTableModel}: the rows, the pivot ({@link PivotConfigIf}) and the look of the
12
+ * table.
13
+ *
14
+ * @example
15
+ * const para: PivotTableModelParaIf<Sale> = {
16
+ * rows: sales,
17
+ * rowFields: ["region", "produkt"],
18
+ * columnFields: [{ value: "monat", order: "insertion" }],
19
+ * values: ["umsatz"],
20
+ * emptyText: "–",
21
+ * valueColumnWidth: px80
22
+ * };
23
+ * const tableModel = PivotFactory.createTableModel(para);
24
+ *
25
+ * @typeParam T - the row type
26
+ * @since 1.1.94
27
+ * @category Pivot
28
+ */
29
+ export interface PivotTableModelParaIf<T = any> extends PivotConfigIf<T> {
30
+ /** The source rows (not changed). */
31
+ rows: T[];
32
+ /** The group column (arrows and row labels): header, width, `showCount` (default false), own label, fixed (default true). */
33
+ groupColumn?: Partial<RowGroupColumnOptionsIf<T>>;
34
+ /** The width of a value column. Default 100 px. */
35
+ valueColumnWidth?: SizeIf;
36
+ /** Row numbers in front of the group column (counted per displayed row). */
37
+ rowNumbersVisible?: boolean | Partial<RowNumberOptionsIf>;
38
+ /** Options of the table (row heights, selection model …). */
39
+ tableOptions?: TableOptionsIf;
40
+ /** Row heights of header, body and footer. Default: `tableOptions.defaultRowHeights`. */
41
+ defaultRowHeights?: DefaultRowHeightsIf;
42
+ /** Selection model factory. Default: `tableOptions.getSelectionModel`. */
43
+ getSelectionModel?: GetT<SelectionModelIf>;
44
+ }
@@ -0,0 +1,70 @@
1
+ import { TableModelIf } from '../data/tablemodel/table-model.if.js';
2
+ import { RowGroupTableModelParaIf } from './row-group-table-model-para.if.js';
3
+
4
+ /**
5
+ * Creates a table model that groups flat rows: one row per group with an arrow, a label (`"Nord (3)"`) and
6
+ * aggregates per column, below it the sub-groups or the original rows – plus a total row. MIT-licensed, no
7
+ * dependencies, built on the tree table ({@link AreaModelRowGroups}).
8
+ *
9
+ * What the factory does:
10
+ * - puts a {@link RowGroupColumnDef} (arrows, labels) behind row numbers and checkbox column and fixes it on the left;
11
+ * - hides the columns of grouped properties (`hideGroupedColumns`), their values are in the group column;
12
+ * - builds the body ({@link AreaModelRowGroups}) and a total row from the `aggregations` ({@link AreaModelAggregateFooter}).
13
+ *
14
+ * Regroup at runtime with {@link TableApi.setRowGroupBy}; filter, sort, edit and export as in any table.
15
+ *
16
+ * @example
17
+ * // R1: sales by region, sum of umsatz, total row
18
+ * const tableModel = RowGroupFactory.createTableModel<Sale>({
19
+ * rows: sales,
20
+ * columnDefs,
21
+ * groupBy: ["region"],
22
+ * aggregations: { umsatz: "sum" }
23
+ * });
24
+ * // ▾ Nord (3) … 270 · A Jan 100 · B Jan 50 · A Feb 120 · ▾ Süd (3) … 180 · … · footer "6 rows" … 450
25
+ *
26
+ * @example
27
+ * // two levels, all groups closed, regroup with a select
28
+ * const tableModel = RowGroupFactory.createTableModel<Order>({
29
+ * rows: orders, columnDefs, groupBy: ["country", "city"],
30
+ * aggregations: { quantity: "sum", total: "sum" }, expanded: 0
31
+ * });
32
+ * select.addEventListener("change", () => tableApi.setRowGroupBy(select.value.split(",")));
33
+ *
34
+ * @see Training: [Row grouping](https://gui.expert/training/row-grouping/)
35
+ *
36
+ * @since 1.1.94
37
+ * @category Row grouping
38
+ */
39
+ export declare class RowGroupFactory {
40
+ /**
41
+ * Creates the table model of a row grouping (see the class description).
42
+ *
43
+ * @param {RowGroupTableModelParaIf<T>} p - rows, levels, columns, aggregations, options
44
+ * @returns {TableModelIf} the table model – its body is an {@link AreaModelRowGroups}
45
+ *
46
+ * @example
47
+ * const tableModel = RowGroupFactory.createTableModel<Sale>({
48
+ * rows: sales, columnDefs, groupBy: ["region", "produkt"], aggregations: { umsatz: "avg" }
49
+ * });
50
+ * const body = tableModel.getBodyModel() as AreaModelRowGroups<Sale>;
51
+ * body.getGroups()[0].results.umsatz; // 90 – Nord, the average of its three rows
52
+ */
53
+ static createTableModel<T = any>(p: RowGroupTableModelParaIf<T>): TableModelIf;
54
+ /**
55
+ * Hides the columns of the grouped properties (and shows the ones that are no longer grouped) and sets the header of
56
+ * the group column – after `body.setGroupBy(…)`. {@link TableApi.setRowGroupBy} calls it for you.
57
+ *
58
+ * @param {TableModelIf} tableModel - a table model of {@link createTableModel}
59
+ * @returns {boolean} false if the body is no row grouping
60
+ *
61
+ * @example
62
+ * const body = tableModel.getBodyModel() as AreaModelRowGroups<Sale>;
63
+ * body.setGroupBy(["produkt"]);
64
+ * RowGroupFactory.updateLayout(tableModel);
65
+ * tableApi.recalcWrappers();
66
+ * tableApi.repaintHard();
67
+ */
68
+ static updateLayout(tableModel: TableModelIf): boolean;
69
+ private static footerConfig;
70
+ }
@@ -0,0 +1,66 @@
1
+ import { ColumnDefIf } from '../data/tablemodel/column/column-def.if.js';
2
+ import { TableOptionsIf } from '../data/options/table-options.if.js';
3
+ import { DefaultRowHeightsIf } from '../data/options/default-row-heights.if.js';
4
+ import { GetT } from '../data/common/get-t.js';
5
+ import { SelectionModelIf } from '../selection/selection-model.if.js';
6
+ import { RowNumberOptionsIf } from '../data/common/row-number-options.if.js';
7
+ import { AggregateSpecMap } from '../aggregation/aggregate-spec.if.js';
8
+ import { AggregateFooterConfig } from '../data/tablemodel/areamodel/aggregate-footer-options.if.js';
9
+ import { RowGroupField } from '../rowgroup/row-group-field.type.js';
10
+ import { RowGroupColumnOptionsIf } from '../rowgroup/row-group-column-options.if.js';
11
+ import { RowGroupExpanded } from '../rowgroup/row-group-model-options.if.js';
12
+
13
+ /**
14
+ * Parameters of {@link RowGroupFactory.createTableModel}.
15
+ *
16
+ * @example
17
+ * const para: RowGroupTableModelParaIf<Sale> = {
18
+ * rows: sales,
19
+ * columnDefs,
20
+ * groupBy: ["region", "produkt"],
21
+ * aggregations: { umsatz: "sum" },
22
+ * expanded: 1 // regions open, products closed
23
+ * };
24
+ * const tableModel = RowGroupFactory.createTableModel(para);
25
+ *
26
+ * @typeParam T - the row type
27
+ * @since 1.1.94
28
+ * @category Row grouping
29
+ */
30
+ export interface RowGroupTableModelParaIf<T = any> {
31
+ /** The flat rows (not changed). */
32
+ rows: T[];
33
+ /** The levels, top first: property paths (`"region"`), functions (`o => o.date.getFullYear()`) or {@link RowGroupFieldIf}. */
34
+ groupBy: RowGroupField<T>[];
35
+ /** The columns. Default: one per key of the first row. The group column is added in front of them. */
36
+ columnDefs?: ColumnDefIf[];
37
+ /** Aggregates in the group rows; the key is the property of the column (`{ umsatz: "sum" }`, see {@link AggregateSpecMap}). */
38
+ aggregations?: AggregateSpecMap<T>;
39
+ /**
40
+ * The total row: `true` = the `aggregations`, `false` = none, or an own configuration ({@link AggregateFooterConfig}).
41
+ * Default: `true` if there are aggregations.
42
+ */
43
+ footerAggregates?: boolean | AggregateFooterConfig<T>;
44
+ /** The group column: header, width, count, own label, fixed. */
45
+ groupColumn?: Partial<RowGroupColumnOptionsIf<T>>;
46
+ /** Hide the columns of the grouped properties. Default true. */
47
+ hideGroupedColumns?: boolean;
48
+ /** Expanded groups: `true` (default), `false`, open levels as a number (`0` = all closed) or a function per group. */
49
+ expanded?: RowGroupExpanded<T>;
50
+ /** The label of the group of empty keys (`null`, `undefined`, `""`). Default `"(empty)"`. */
51
+ emptyLabel?: string;
52
+ /** Options of the table (row heights, selection model, filter …). */
53
+ tableOptions?: TableOptionsIf;
54
+ /** Columns fixed on the left besides the group column (which is fixed by itself). Default 0. */
55
+ fixedLeftColumnCount?: number;
56
+ /** Columns fixed on the right. Default 0. */
57
+ fixedRightColumnCount?: number;
58
+ /** Row numbers in front of all columns (counted per displayed row, group rows included). */
59
+ rowNumbersVisible?: boolean | Partial<RowNumberOptionsIf>;
60
+ /** Set with a {@link CheckboxColumnDef} in `columnDefs` (default: detected). */
61
+ rowCheckboxVisible?: boolean;
62
+ /** Row heights of header, body and footer. Default: `tableOptions.defaultRowHeights`. */
63
+ defaultRowHeights?: DefaultRowHeightsIf;
64
+ /** Selection model factory. Default: `tableOptions.getSelectionModel`. */
65
+ getSelectionModel?: GetT<SelectionModelIf>;
66
+ }