@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.
- package/README.md +13 -0
- package/css/main.css +142 -0
- package/css/table-classes.css +34 -0
- package/css/table-color-vars.css +32 -0
- package/css/table-pivot.css +42 -0
- package/css/table-row-group.css +25 -0
- package/css/table-tree.css +9 -0
- package/index.cjs +44 -36
- package/index.d.ts +39 -0
- package/index.js +6247 -3509
- package/lib/table/aggregation/aggregate-internals.d.ts +30 -0
- package/lib/table/aggregation/aggregate-spec.if.d.ts +51 -0
- package/lib/table/aggregation/aggregate.d.ts +58 -0
- package/lib/table/aggregation/aggregator-name.type.d.ts +39 -0
- package/lib/table/aggregation/aggregator.if.d.ts +51 -0
- package/lib/table/aggregation/aggregators.d.ts +37 -0
- package/lib/table/aggregation/create-value-getter.d.ts +18 -0
- package/lib/table/aggregation/selection-aggregates.if.d.ts +35 -0
- package/lib/table/aggregation/value-accessor.type.d.ts +13 -0
- package/lib/table/data/common/row-number-column-def.d.ts +107 -0
- package/lib/table/data/common/row-number-columns.d.ts +17 -0
- package/lib/table/data/common/row-number-options.d.ts +19 -0
- package/lib/table/data/common/row-number-options.if.d.ts +44 -0
- package/lib/table/data/tablemodel/areamodel/aggregate-footer-options.if.d.ts +73 -0
- package/lib/table/data/tablemodel/areamodel/area-model-aggregate-footer.d.ts +145 -0
- package/lib/table/data/tablemodel/areamodel/area-model-cell-groups.d.ts +18 -1
- package/lib/table/data/tablemodel/areamodel/area-model-pivot-footer.d.ts +47 -0
- package/lib/table/data/tablemodel/areamodel/area-model-pivot.d.ts +250 -0
- package/lib/table/data/tablemodel/areamodel/area-model-row-groups.d.ts +314 -0
- package/lib/table/data/tablemodel/areamodel/area-model-tree.d.ts +14 -0
- package/lib/table/data/tablemodel/areamodel/area-model.if.d.ts +39 -0
- package/lib/table/data/tablemodel/areamodel/derived-values-change.if.d.ts +30 -0
- package/lib/table/factory/pivot-factory.d.ts +63 -0
- package/lib/table/factory/pivot-table-model-para.if.d.ts +44 -0
- package/lib/table/factory/row-group-factory.d.ts +70 -0
- package/lib/table/factory/row-group-table-model-para.if.d.ts +66 -0
- package/lib/table/factory/table-factory.d.ts +45 -1
- package/lib/table/instanceof-workaround.d.ts +143 -6
- package/lib/table/pivot/area-model-pivot-header.d.ts +9 -0
- package/lib/table/pivot/pivot-column-def.d.ts +28 -0
- package/lib/table/pivot/pivot-column-info.if.d.ts +25 -0
- package/lib/table/pivot/pivot-config.if.d.ts +58 -0
- package/lib/table/pivot/pivot-internals.d.ts +13 -0
- package/lib/table/pivot/pivot-layout.d.ts +40 -0
- package/lib/table/pivot/pivot-result.d.ts +157 -0
- package/lib/table/pivot/pivot-table-model.d.ts +86 -0
- package/lib/table/pivot/pivot-value.type.d.ts +67 -0
- package/lib/table/render-scope.d.ts +61 -4
- package/lib/table/renderer/aggregate-cell-renderer.d.ts +27 -0
- package/lib/table/renderer/row-number-cell-renderer.d.ts +27 -0
- package/lib/table/rowgroup/group-rows-internals.d.ts +14 -0
- package/lib/table/rowgroup/group-rows-options.if.d.ts +13 -0
- package/lib/table/rowgroup/group-rows.d.ts +48 -0
- package/lib/table/rowgroup/row-group-column-def.d.ts +26 -0
- package/lib/table/rowgroup/row-group-column-options.if.d.ts +27 -0
- package/lib/table/rowgroup/row-group-detail-row.d.ts +8 -0
- package/lib/table/rowgroup/row-group-field.type.d.ts +56 -0
- package/lib/table/rowgroup/row-group-internals.d.ts +31 -0
- package/lib/table/rowgroup/row-group-layout.d.ts +13 -0
- package/lib/table/rowgroup/row-group-model-options.if.d.ts +39 -0
- package/lib/table/rowgroup/row-group.d.ts +51 -0
- package/lib/table/rowgroup/row-group.if.d.ts +48 -0
- package/lib/table/selection/selection-aggregator.d.ts +37 -0
- package/lib/table/selection/selection-service.d.ts +7 -0
- package/lib/table/service/convenience-dom.service.d.ts +5 -0
- package/lib/table/table-api.d.ts +112 -1
- package/lib/table/table-scope.d.ts +19 -0
- package/package.json +3 -2
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AggregatorIf } from './aggregator.if.js';
|
|
2
|
+
import { AggregateSpec } from './aggregate-spec.if.js';
|
|
3
|
+
import { ValueAccessor } from './value-accessor.type.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A spec of {@link AggregateSpecMap} resolved for a pass over rows.
|
|
7
|
+
*/
|
|
8
|
+
export interface ResolvedAggregateSpec<T = any> {
|
|
9
|
+
key: string;
|
|
10
|
+
aggregator: AggregatorIf;
|
|
11
|
+
/** where the value comes from: the spec's `value`, otherwise the key as property path */
|
|
12
|
+
value: ValueAccessor<T>;
|
|
13
|
+
/** undefined for `count` (the value does not matter) */
|
|
14
|
+
getValue?: (row: T) => unknown;
|
|
15
|
+
format?: (result: unknown) => string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Resolves one entry of an {@link AggregateSpecMap}: the aggregator object, the value getter (default: the key as
|
|
19
|
+
* property path) and the format.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveAggregateSpec<T>(key: string, spec: AggregateSpec<T>): ResolvedAggregateSpec<T>;
|
|
22
|
+
/**
|
|
23
|
+
* Runs resolved specs over the rows in one pass and returns the states (for `merge` by the caller) – the core of
|
|
24
|
+
* {@link aggregateColumns} and of {@link AreaModelAggregateFooter}.
|
|
25
|
+
*/
|
|
26
|
+
export declare function aggregateStates<T>(rows: ArrayLike<T>, specs: ResolvedAggregateSpec<T>[]): unknown[];
|
|
27
|
+
/**
|
|
28
|
+
* Like {@link aggregateStates} for models without row objects: the getters get the row index 0 … rowCount-1.
|
|
29
|
+
*/
|
|
30
|
+
export declare function aggregateIndexStates(rowCount: number, specs: Pick<ResolvedAggregateSpec<number>, 'aggregator' | 'getValue'>[]): unknown[];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { AggregatorRef } from './aggregator-name.type.js';
|
|
2
|
+
import { ValueAccessor } from './value-accessor.type.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* One aggregate in full form: which aggregator, which value, and (in a footer) how it is shown.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const spec: AggregateSpecIf<Order> = {
|
|
9
|
+
* aggregator: "avg",
|
|
10
|
+
* value: "total", // default: the key of the spec
|
|
11
|
+
* format: v => `${(v as number).toFixed(2)} €` // footer text; Excel gets the number
|
|
12
|
+
* };
|
|
13
|
+
*
|
|
14
|
+
* @since 1.1.94
|
|
15
|
+
* @category Aggregation
|
|
16
|
+
*/
|
|
17
|
+
export interface AggregateSpecIf<T = any> {
|
|
18
|
+
/** A built-in aggregator by name or an own one. */
|
|
19
|
+
aggregator: AggregatorRef;
|
|
20
|
+
/** The value of a row. Default: the key of the spec as property path. */
|
|
21
|
+
value?: ValueAccessor<T>;
|
|
22
|
+
/** The text in a footer cell. Default: numbers with at most two decimals (`"1,234.5"`), dates as date, `undefined` empty. */
|
|
23
|
+
format?: (result: unknown) => string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* An aggregate in short form (`"sum"`, an own aggregator) or in full form ({@link AggregateSpecIf}).
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* const short: AggregateSpec<Order> = "sum";
|
|
30
|
+
* const full: AggregateSpec<Order> = { aggregator: "sum", value: o => o.quantity * o.price };
|
|
31
|
+
*
|
|
32
|
+
* @since 1.1.94
|
|
33
|
+
* @category Aggregation
|
|
34
|
+
*/
|
|
35
|
+
export type AggregateSpec<T = any> = AggregatorRef | AggregateSpecIf<T>;
|
|
36
|
+
/**
|
|
37
|
+
* Several aggregates by key. In {@link aggregateColumns} the key names the result (and is the default property path);
|
|
38
|
+
* in a footer ({@link AreaModelAggregateFooter}) the key is the property of the column that shows the result.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* const specs: AggregateSpecMap<Order> = {
|
|
42
|
+
* quantity: "sum",
|
|
43
|
+
* total: "sum",
|
|
44
|
+
* customer: "countDistinct",
|
|
45
|
+
* avgTotal: { aggregator: "avg", value: "total" } // a key without a column: only in the results
|
|
46
|
+
* };
|
|
47
|
+
*
|
|
48
|
+
* @since 1.1.94
|
|
49
|
+
* @category Aggregation
|
|
50
|
+
*/
|
|
51
|
+
export type AggregateSpecMap<T = any> = Record<string, AggregateSpec<T>>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { AggregatorRef } from './aggregator-name.type.js';
|
|
2
|
+
import { ValueAccessor } from './value-accessor.type.js';
|
|
3
|
+
import { AggregateSpecMap } from './aggregate-spec.if.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Aggregates one value of all rows.
|
|
7
|
+
*
|
|
8
|
+
* @param {ArrayLike<T>} rows - the rows (an array, also filtered or sorted ones)
|
|
9
|
+
* @param {ValueAccessor<T>} value - property path (`"total"`, `"address.zip"`) or function
|
|
10
|
+
* @param {AggregatorRef} aggregator - `"sum"`, `"count"`, `"avg"`, `"min"`, `"max"`, `"countDistinct"`, `"first"`,
|
|
11
|
+
* `"last"` or an own {@link AggregatorIf}
|
|
12
|
+
* @returns {unknown} the result (`sum` of no numbers is `0`, `avg`/`min`/`max` of no numbers `undefined`)
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* aggregate(orders, "total", "sum"); // all orders
|
|
16
|
+
* aggregate(orders.filter(o => o.city === "Berlin"), "total", "avg");
|
|
17
|
+
* aggregate(orders, o => o.quantity * o.price, "max"); // a computed value
|
|
18
|
+
*
|
|
19
|
+
* @since 1.1.94
|
|
20
|
+
* @category Aggregation
|
|
21
|
+
*/
|
|
22
|
+
export declare function aggregate<T = any>(rows: ArrayLike<T>, value: ValueAccessor<T>, aggregator: AggregatorRef): unknown;
|
|
23
|
+
/**
|
|
24
|
+
* Aggregates several values in **one** pass over the rows. The keys of `specs` name the results; a key is also the
|
|
25
|
+
* default property path.
|
|
26
|
+
*
|
|
27
|
+
* @param {ArrayLike<T>} rows - the rows
|
|
28
|
+
* @param {AggregateSpecMap<T>} specs - key → aggregator name, own aggregator or {@link AggregateSpecIf}
|
|
29
|
+
* @returns {Record<string, unknown>} key → result, in the order of `specs`
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* const totals = aggregateColumns(orders, {
|
|
33
|
+
* quantity: "sum",
|
|
34
|
+
* total: "sum",
|
|
35
|
+
* customer: "countDistinct",
|
|
36
|
+
* avgTotal: { aggregator: "avg", value: "total" }
|
|
37
|
+
* });
|
|
38
|
+
* // { quantity: 549812, total: 27481033.5, customer: 412, avgTotal: 274.81 }
|
|
39
|
+
*
|
|
40
|
+
* @since 1.1.94
|
|
41
|
+
* @category Aggregation
|
|
42
|
+
*/
|
|
43
|
+
export declare function aggregateColumns<T = any>(rows: ArrayLike<T>, specs: AggregateSpecMap<T>): Record<string, unknown>;
|
|
44
|
+
/**
|
|
45
|
+
* The default text of an aggregate (e.g. in a footer cell or a status bar): numbers with at most two decimals and
|
|
46
|
+
* thousands separators, dates as date (en-US), `null`/`undefined` empty, everything else as text.
|
|
47
|
+
*
|
|
48
|
+
* @param {unknown} value - an aggregation result
|
|
49
|
+
* @returns {string} the text
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* formatAggregateValue(1234.5678); // "1,234.57"
|
|
53
|
+
* formatAggregateValue(undefined); // ""
|
|
54
|
+
*
|
|
55
|
+
* @since 1.1.94
|
|
56
|
+
* @category Aggregation
|
|
57
|
+
*/
|
|
58
|
+
export declare function formatAggregateValue(value: unknown): string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { AggregatorIf } from './aggregator.if.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The names of the built-in aggregators ({@link Aggregators}).
|
|
5
|
+
*
|
|
6
|
+
* | name | result | uses |
|
|
7
|
+
* |---|---|---|
|
|
8
|
+
* | `sum` | sum, `0` for no numbers | numbers (`NaN` ignored) |
|
|
9
|
+
* | `count` | number of rows | every row – the value does not matter |
|
|
10
|
+
* | `avg` | sum / count of the numbers, `undefined` for none | numbers |
|
|
11
|
+
* | `min`, `max` | smallest / largest value, `undefined` for none | numbers and valid `Date`s |
|
|
12
|
+
* | `countDistinct` | number of different values | everything except `null`/`undefined`; dates by time |
|
|
13
|
+
* | `first`, `last` | value of the first / last row | every row, the value as it is |
|
|
14
|
+
*
|
|
15
|
+
* Text is never converted to a number (like Excel's SUM over a range): `"12"` does not count. Use an accessor such as
|
|
16
|
+
* `row => Number(row.amount)` for numbers stored as text.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* const name: AggregatorName = "avg";
|
|
20
|
+
* aggregate(orders, "total", name);
|
|
21
|
+
*
|
|
22
|
+
* @since 1.1.94
|
|
23
|
+
* @category Aggregation
|
|
24
|
+
*/
|
|
25
|
+
export type AggregatorName = "sum" | "count" | "avg" | "min" | "max" | "countDistinct" | "first" | "last";
|
|
26
|
+
/**
|
|
27
|
+
* A built-in aggregator by name or an own {@link AggregatorIf}.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* const byName: AggregatorRef = "sum";
|
|
31
|
+
* const own: AggregatorRef = { name: "range", init: () => [Infinity, -Infinity],
|
|
32
|
+
* add: ([lo, hi]: number[], v: unknown) => typeof v === "number" ? [Math.min(lo, v), Math.max(hi, v)] : [lo, hi],
|
|
33
|
+
* merge: (a: number[], b: number[]) => [Math.min(a[0], b[0]), Math.max(a[1], b[1])],
|
|
34
|
+
* result: ([lo, hi]: number[]) => hi - lo };
|
|
35
|
+
*
|
|
36
|
+
* @since 1.1.94
|
|
37
|
+
* @category Aggregation
|
|
38
|
+
*/
|
|
39
|
+
export type AggregatorRef = AggregatorName | AggregatorIf;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An aggregation (sum, average, …) as four small functions over a **state**: `init` → `add` per value → `result`.
|
|
3
|
+
* `merge` combines two partial states – the result is the same as one pass over all values. That makes groups
|
|
4
|
+
* (row grouping, pivot) cheap: the state of a group is the merge of the states of its sub-groups, and an average of
|
|
5
|
+
* averages stays correct (`{ sum, count }` instead of a number).
|
|
6
|
+
*
|
|
7
|
+
* The built-in aggregators are in {@link Aggregators}; an own aggregator implements this interface and can be used
|
|
8
|
+
* everywhere a name like `"sum"` is accepted ({@link AggregatorRef}).
|
|
9
|
+
*
|
|
10
|
+
* `add(state, …)` and `merge(a, b)` may change `state` / `a` and return it (no allocation per row with millions of
|
|
11
|
+
* rows); `merge` never changes `b`, so a child state can be merged into several parents. Call `init()` for a fresh
|
|
12
|
+
* state, e.g. `merge(init(), child)` for a parent that must not change the child.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // the product of all numbers
|
|
16
|
+
* const product: AggregatorIf<number, number> = {
|
|
17
|
+
* name: "product",
|
|
18
|
+
* init: () => 1,
|
|
19
|
+
* add: (state, value) => typeof value === "number" ? state * value : state,
|
|
20
|
+
* merge: (a, b) => a * b,
|
|
21
|
+
* result: state => state
|
|
22
|
+
* };
|
|
23
|
+
* aggregate([{ f: 2 }, { f: 3 }, { f: 7 }], "f", product); // 42
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* // partial results: the average of two groups is the average of all rows
|
|
27
|
+
* const avg = Aggregators.avg;
|
|
28
|
+
* const north = [100, 50, 120].reduce((s, v) => avg.add(s, v), avg.init());
|
|
29
|
+
* const south = [80, 70, 30].reduce((s, v) => avg.add(s, v), avg.init());
|
|
30
|
+
* avg.result(avg.merge(north, south)); // 75 – not the mean of 90 and 60
|
|
31
|
+
*
|
|
32
|
+
* @typeParam S - the state
|
|
33
|
+
* @typeParam R - the result
|
|
34
|
+
* @since 1.1.94
|
|
35
|
+
* @category Aggregation
|
|
36
|
+
*/
|
|
37
|
+
export interface AggregatorIf<S = any, R = unknown> {
|
|
38
|
+
/** The name, e.g. `"sum"` – the built-in ones use their {@link AggregatorName}. */
|
|
39
|
+
readonly name: string;
|
|
40
|
+
/** The state of the empty set. */
|
|
41
|
+
init(): S;
|
|
42
|
+
/** Takes one value into the state (values the aggregator can't use are ignored); returns the state. */
|
|
43
|
+
add(state: S, value: unknown): S;
|
|
44
|
+
/**
|
|
45
|
+
* Combines two partial states (`a` first, then `b` – matters for `first`/`last`); may change `a` and returns the
|
|
46
|
+
* combined state; never changes `b`.
|
|
47
|
+
*/
|
|
48
|
+
merge(a: S, b: S): S;
|
|
49
|
+
/** The result of a state, e.g. `sum / count` for the average. */
|
|
50
|
+
result(state: S): R;
|
|
51
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { AggregatorIf } from './aggregator.if.js';
|
|
2
|
+
import { AggregatorName, AggregatorRef } from './aggregator-name.type.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The built-in aggregators by name ({@link AggregatorName}) – each one is an {@link AggregatorIf}, so `merge` combines
|
|
6
|
+
* partial results correctly (the average of groups is the average of all rows).
|
|
7
|
+
*
|
|
8
|
+
* - `sum` and `avg` sum with compensation (Neumaier): `0.1 + 0.2 + 0.3` is `0.6`, and splitting the data into groups
|
|
9
|
+
* does not change the result.
|
|
10
|
+
* - Numeric aggregators use numbers only (`NaN`, text, `null` … are ignored); `min`/`max` also valid `Date`s;
|
|
11
|
+
* `count` counts rows; `countDistinct` ignores `null`/`undefined`; `first`/`last` take the value as it is.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const { sum } = Aggregators;
|
|
15
|
+
* let state = sum.init();
|
|
16
|
+
* for (const order of orders) state = sum.add(state, order.total);
|
|
17
|
+
* sum.result(state); // the same as aggregate(orders, "total", "sum")
|
|
18
|
+
*
|
|
19
|
+
* @since 1.1.94
|
|
20
|
+
* @category Aggregation
|
|
21
|
+
*/
|
|
22
|
+
export declare const Aggregators: Readonly<Record<AggregatorName, AggregatorIf>>;
|
|
23
|
+
/**
|
|
24
|
+
* The aggregator for a name, or the given aggregator itself.
|
|
25
|
+
*
|
|
26
|
+
* @param {AggregatorRef} aggregator - a name like `"sum"` or an own {@link AggregatorIf}
|
|
27
|
+
* @returns {AggregatorIf} the aggregator
|
|
28
|
+
* @throws {Error} for an unknown name (the message lists the built-in names)
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* getAggregator("avg").name; // "avg"
|
|
32
|
+
* getAggregator(Aggregators.max); // the same object
|
|
33
|
+
*
|
|
34
|
+
* @since 1.1.94
|
|
35
|
+
* @category Aggregation
|
|
36
|
+
*/
|
|
37
|
+
export declare function getAggregator(aggregator: AggregatorRef): AggregatorIf;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ValueAccessor } from './value-accessor.type.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A function that reads a value from a row: a property path (`"address.city"`, missing parents → `undefined`) or the
|
|
5
|
+
* given function itself.
|
|
6
|
+
*
|
|
7
|
+
* @param {ValueAccessor<T>} value - property path or function
|
|
8
|
+
* @returns {(row: T) => unknown} the getter
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const city = createValueGetter<{ address?: { city: string } }>("address.city");
|
|
12
|
+
* city({ address: { city: "Kiel" } }); // "Kiel"
|
|
13
|
+
* city({}); // undefined
|
|
14
|
+
*
|
|
15
|
+
* @since 1.1.94
|
|
16
|
+
* @category Aggregation
|
|
17
|
+
*/
|
|
18
|
+
export declare function createValueGetter<T = any>(value: ValueAccessor<T>): (row: T) => unknown;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Count, sum, average, minimum and maximum of the selected body cells – for a status bar like in a spreadsheet
|
|
3
|
+
* ({@link TableApi.getSelectionAggregates}).
|
|
4
|
+
*
|
|
5
|
+
* Counted are the selected cells of the visible data columns (row number and checkbox columns not); `numericCount`,
|
|
6
|
+
* `sum`, `avg`, `min`, `max` use the cells whose value is a number.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const a: SelectionAggregatesIf = tableApi.getSelectionAggregates();
|
|
10
|
+
* const text = a.capped ? "selection too large"
|
|
11
|
+
* : a.numericCount ? `Count ${a.count} · Sum ${formatAggregateValue(a.sum)} · Avg ${formatAggregateValue(a.avg)}`
|
|
12
|
+
* : a.count ? `Count ${a.count}` : "";
|
|
13
|
+
*
|
|
14
|
+
* @since 1.1.94
|
|
15
|
+
* @category Aggregation
|
|
16
|
+
*/
|
|
17
|
+
export interface SelectionAggregatesIf {
|
|
18
|
+
/** Number of selected cells. */
|
|
19
|
+
count: number;
|
|
20
|
+
/** Number of selected cells with a number. */
|
|
21
|
+
numericCount: number;
|
|
22
|
+
/** Sum of the numbers (`0` without numbers). */
|
|
23
|
+
sum: number;
|
|
24
|
+
/** Average of the numbers (`undefined` without numbers). */
|
|
25
|
+
avg: number | undefined;
|
|
26
|
+
/** Smallest number (`undefined` without numbers). */
|
|
27
|
+
min: number | undefined;
|
|
28
|
+
/** Largest number (`undefined` without numbers). */
|
|
29
|
+
max: number | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* `true` if the selection covers more than {@link SelectionAggregator.maxCells} cells: then nothing is computed
|
|
32
|
+
* (all values 0 / `undefined`) – a partial sum would be wrong.
|
|
33
|
+
*/
|
|
34
|
+
capped: boolean;
|
|
35
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where an aggregation gets its value from a row: a property path (`"total"`, `"address.city"`) or a function
|
|
3
|
+
* (computed values, numbers stored as text, …).
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* const byPath: ValueAccessor<Order> = "total";
|
|
7
|
+
* const computed: ValueAccessor<Order> = order => order.quantity * order.price;
|
|
8
|
+
*
|
|
9
|
+
* @typeParam T - the row type
|
|
10
|
+
* @since 1.1.94
|
|
11
|
+
* @category Aggregation
|
|
12
|
+
*/
|
|
13
|
+
export type ValueAccessor<T = any> = string | ((row: T) => unknown);
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { SizeIf } from './size.if.js';
|
|
2
|
+
import { ColumnDefIf } from '../tablemodel/column/column-def.if.js';
|
|
3
|
+
import { CellRendererIf } from '../../renderer/cell-render.if.js';
|
|
4
|
+
import { AreaObjectMapType } from './area-map.type.js';
|
|
5
|
+
import { AreaModelIf } from '../tablemodel/areamodel/area-model.if.js';
|
|
6
|
+
import { BooleanFunction } from './boolean-function.js';
|
|
7
|
+
import { RowNumberOptionsIf } from './row-number-options.if.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A built-in column with running row numbers (1, 2, 3 …) like the row headers of a spreadsheet.
|
|
11
|
+
*
|
|
12
|
+
* The easy way is the factory flag: `TableFactory.createTableModel({ rows, rowNumbersVisible: true })` puts this column
|
|
13
|
+
* in front of the others and fixes it on the left. Put it into `columnDefs` yourself if you build the models yourself
|
|
14
|
+
* – as the **first** column, counted in `fixedLeftColumnCount`.
|
|
15
|
+
*
|
|
16
|
+
* What the table does with it:
|
|
17
|
+
* - the width follows the largest number (`"9"` → `"2000000"`) – after `setRows`, filtering, expanding tree rows;
|
|
18
|
+
* - not sortable, not editable, no resize handle, can't be dragged, no column can be dragged in front of it;
|
|
19
|
+
* - body cells have `role="rowheader"` – screen readers announce the number with the row;
|
|
20
|
+
* - a click on a number selects the row (selection type `"row"` and `"range"`), Shift extends, Ctrl/⌘ toggles;
|
|
21
|
+
* a click on the header cell selects all;
|
|
22
|
+
* - copy ({@link TableApi.copyToClipboard}) and Excel export ({@link TableApi.downloadExcel}) leave it out, unless
|
|
23
|
+
* `exported: true`;
|
|
24
|
+
* - with a {@link CheckboxColumnDef} directly behind it, the checkbox, the tree arrow and
|
|
25
|
+
* `showCheckboxWihoutExtraColumn` move one column to the right.
|
|
26
|
+
*
|
|
27
|
+
* Note: it is a real column – the indices of all other columns are one higher (like with a `CheckboxColumnDef`).
|
|
28
|
+
* Use properties where you can (`tableApi.setColumnVisible("total", false)`, {@link TableApi.getColumnIndex}).
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* const withNumbers: ColumnDefIf[] = [new RowNumberColumnDef(), new CheckboxColumnDef(), ...columnDefs];
|
|
32
|
+
* const model = TableFactory.createTableModel({ rows: orders, columnDefs: withNumbers, fixedLeftColumnCount: 2 });
|
|
33
|
+
*
|
|
34
|
+
* @see Training: [Fixed columns](https://gui.expert/training/fixed-columns/)
|
|
35
|
+
*
|
|
36
|
+
* @since 1.1.93
|
|
37
|
+
* @category Row numbers
|
|
38
|
+
*/
|
|
39
|
+
export declare class RowNumberColumnDef implements ColumnDefIf {
|
|
40
|
+
/** Marker for {@link isRowNumberColumnDef} (works across copies of the core). */
|
|
41
|
+
readonly type = "RowNumberColumnDef";
|
|
42
|
+
property: string;
|
|
43
|
+
headerLabel: string;
|
|
44
|
+
/** Starts with `minWidthInPx`; the table widens it to fit the largest number. */
|
|
45
|
+
width: SizeIf;
|
|
46
|
+
minWidth: SizeIf;
|
|
47
|
+
maxWidth: SizeIf;
|
|
48
|
+
rendererMap: AreaObjectMapType<CellRendererIf>;
|
|
49
|
+
classes: AreaObjectMapType<string[]>;
|
|
50
|
+
isVisible: BooleanFunction;
|
|
51
|
+
/** The options (defaults from {@link RowNumberOptions}). */
|
|
52
|
+
readonly options: RowNumberOptionsIf;
|
|
53
|
+
/**
|
|
54
|
+
* "original": position of each row in getAllRows() – built when needed, for one array with one length. Checked on
|
|
55
|
+
* every lookup (`rows[position] === row`): rows sorted or replaced in place rebuild it.
|
|
56
|
+
*/
|
|
57
|
+
private originalIndex?;
|
|
58
|
+
constructor(options?: Partial<RowNumberOptionsIf>);
|
|
59
|
+
/**
|
|
60
|
+
* The number of a body row (1-based): its position in the view (`"display"`) or in `getAllRows()` (`"original"`).
|
|
61
|
+
*
|
|
62
|
+
* @param {number} rowIndex - row index in the body (the current view)
|
|
63
|
+
* @param {AreaModelIf} bodyModel - the body model
|
|
64
|
+
* @returns {number} the row number
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* const columnDef = new RowNumberColumnDef();
|
|
68
|
+
* const rowNumber = columnDef.getRowNumber(0, tableModel.getBodyModel()); // 1
|
|
69
|
+
*/
|
|
70
|
+
getRowNumber(rowIndex: number, bodyModel: AreaModelIf): number;
|
|
71
|
+
/**
|
|
72
|
+
* The text of a row number: `options.format(getRowNumber(…))`.
|
|
73
|
+
*
|
|
74
|
+
* @param {number} rowIndex - row index in the body
|
|
75
|
+
* @param {AreaModelIf} bodyModel - the body model
|
|
76
|
+
* @returns {string} the text shown in the cell (and exported with `exported: true`)
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* const columnDef = new RowNumberColumnDef({ format: n => n.toLocaleString("en-US") });
|
|
80
|
+
* const text = columnDef.getRowNumberText(1999, tableModel.getBodyModel()); // "2,000"
|
|
81
|
+
*/
|
|
82
|
+
getRowNumberText(rowIndex: number, bodyModel: AreaModelIf): string;
|
|
83
|
+
/**
|
|
84
|
+
* The text of the largest number the column can show now – the table measures it for the column width.
|
|
85
|
+
* `"display"`: the row count; `"original"`: the count of all rows (unfiltered). At least the text of 1.
|
|
86
|
+
*
|
|
87
|
+
* @param {AreaModelIf} bodyModel - the body model
|
|
88
|
+
* @returns {string} e.g. `"2000000"`
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* const widest = new RowNumberColumnDef().getWidestText(tableModel.getBodyModel()); // "1000" for 1000 rows
|
|
92
|
+
*/
|
|
93
|
+
getWidestText(bodyModel: AreaModelIf): string;
|
|
94
|
+
/**
|
|
95
|
+
* True if the numbers are shown without an own `format` (plain digits) – the Excel export then writes numbers.
|
|
96
|
+
*
|
|
97
|
+
* @returns {boolean} false with an own `options.format`
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* const plain = new RowNumberColumnDef().hasDefaultFormat(); // true
|
|
101
|
+
*/
|
|
102
|
+
hasDefaultFormat(): boolean;
|
|
103
|
+
private hasAllRows;
|
|
104
|
+
/** Position of a row in getAllRows() (0-based), undefined if the model has no getAllRows() or the row is not in it. */
|
|
105
|
+
private getOriginalPosition;
|
|
106
|
+
private buildOriginalIndex;
|
|
107
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ColumnDefIf } from '../tablemodel/column/column-def.if.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 1 if the first column is a {@link RowNumberColumnDef}, otherwise 0 – the offset of the built-in columns that are
|
|
5
|
+
* bound to "the first column": the {@link CheckboxColumnDef}, the tree arrow and the checkbox of
|
|
6
|
+
* `showCheckboxWihoutExtraColumn` sit at this index (behind the row numbers).
|
|
7
|
+
*
|
|
8
|
+
* @param {ColumnDefIf[] | undefined} columnDefs - the column definitions of the table model
|
|
9
|
+
* @returns {0 | 1} the number of leading row number columns
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const firstDataColumn = getLeadingRowNumberColumnCount(tableModel.getColumnDefs());
|
|
13
|
+
*
|
|
14
|
+
* @since 1.1.93
|
|
15
|
+
* @category Row numbers
|
|
16
|
+
*/
|
|
17
|
+
export declare function getLeadingRowNumberColumnCount(columnDefs: ColumnDefIf[] | undefined): 0 | 1;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { RowNumberMode, RowNumberOptionsIf } from './row-number-options.if.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The default options of the row number column: numbers of the current view, no thousands separators, header `"#"`,
|
|
5
|
+
* not exported, at least 36 px wide.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const columnDef = new RowNumberColumnDef({ ...new RowNumberOptions(), headerLabel: "" });
|
|
9
|
+
*
|
|
10
|
+
* @since 1.1.93
|
|
11
|
+
* @category Row numbers
|
|
12
|
+
*/
|
|
13
|
+
export declare class RowNumberOptions implements RowNumberOptionsIf {
|
|
14
|
+
mode: RowNumberMode;
|
|
15
|
+
format: (rowNumber: number) => string;
|
|
16
|
+
headerLabel: string;
|
|
17
|
+
exported: boolean;
|
|
18
|
+
minWidthInPx: number;
|
|
19
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a {@link RowNumberColumnDef} counts:
|
|
3
|
+
* - `"display"` (default): the position in the current view – 1, 2, 3 … also after sorting and filtering
|
|
4
|
+
* (like the row headers of a spreadsheet).
|
|
5
|
+
* - `"original"`: the position in the unsorted, unfiltered rows (`getAllRows()` of the body model) – the number stays
|
|
6
|
+
* with its row. Body models without `getAllRows()` (tree, own area models) count like `"display"`.
|
|
7
|
+
*
|
|
8
|
+
* @since 1.1.93
|
|
9
|
+
* @category Row numbers
|
|
10
|
+
*/
|
|
11
|
+
export type RowNumberMode = "display" | "original";
|
|
12
|
+
/**
|
|
13
|
+
* Options of the row number column ({@link RowNumberColumnDef}). Pass any subset – to
|
|
14
|
+
* `new RowNumberColumnDef({ … })` or as `rowNumbersVisible: { … }` to {@link TableFactory.createTableModel}.
|
|
15
|
+
* Defaults: {@link RowNumberOptions}.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* const tableModel = TableFactory.createTableModel({
|
|
19
|
+
* rows: orders,
|
|
20
|
+
* rowNumbersVisible: { mode: "original", format: n => n.toLocaleString("en-US"), exported: true }
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* @since 1.1.93
|
|
24
|
+
* @category Row numbers
|
|
25
|
+
*/
|
|
26
|
+
export interface RowNumberOptionsIf {
|
|
27
|
+
/** `"display"` (default) or `"original"`, see {@link RowNumberMode}. */
|
|
28
|
+
mode: RowNumberMode;
|
|
29
|
+
/**
|
|
30
|
+
* The text of a number. Default `String(n)` – no thousands separators (`"2000000"`).
|
|
31
|
+
* The column width is measured with the text of the largest number, so the text should not get shorter for
|
|
32
|
+
* larger numbers.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* const options: Partial<RowNumberOptionsIf> = { format: n => n.toLocaleString("de-DE") }; // "2.000.000"
|
|
36
|
+
*/
|
|
37
|
+
format: (rowNumber: number) => string;
|
|
38
|
+
/** Text of the header cell. Default `"#"` (an empty header cell has no accessible name). */
|
|
39
|
+
headerLabel: string;
|
|
40
|
+
/** `true`: {@link TableApi.copyToClipboard} and {@link TableApi.downloadExcel} contain the numbers. Default `false`. */
|
|
41
|
+
exported: boolean;
|
|
42
|
+
/** The column is never narrower than this (px). Default 36. */
|
|
43
|
+
minWidthInPx: number;
|
|
44
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { AggregateSpecMap } from '../../../aggregation/aggregate-spec.if.js';
|
|
2
|
+
import { AreaModelIf } from './area-model.if.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* One total row of an {@link AreaModelAggregateFooter}: the aggregates by column property and an optional label.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const sumRow: AggregateFooterRowIf<Order> = { label: "Sum", aggregates: { quantity: "sum", total: "sum" } };
|
|
9
|
+
* const avgRow: AggregateFooterRowIf<Order> = {
|
|
10
|
+
* label: (rowCount, results) => `Ø of ${rowCount} orders`,
|
|
11
|
+
* aggregates: { total: { aggregator: "avg", format: v => `${(v as number).toFixed(2)} €` } }
|
|
12
|
+
* };
|
|
13
|
+
*
|
|
14
|
+
* @since 1.1.94
|
|
15
|
+
* @category Aggregation
|
|
16
|
+
*/
|
|
17
|
+
export interface AggregateFooterRowIf<T = any> {
|
|
18
|
+
/** Key = property of the column that shows the result (a key without a column is only in `getResults()`). */
|
|
19
|
+
aggregates: AggregateSpecMap<T>;
|
|
20
|
+
/**
|
|
21
|
+
* Text in the label column (always set as text, never as HTML). Default: `"6 rows"` – or, in a footer with
|
|
22
|
+
* several rows whose aggregates all use the same built-in aggregator, its word (`"Sum"`, `"Average"`, `"Min"` …).
|
|
23
|
+
*/
|
|
24
|
+
label?: string | ((rowCount: number, results: Record<string, unknown>) => string);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The options of an {@link AreaModelAggregateFooter} (and of `TableFactory.createTableModel({ footerAggregates })`).
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* const options: AggregateFooterOptionsIf<Order> = {
|
|
31
|
+
* rows: [
|
|
32
|
+
* { label: "Sum", aggregates: { quantity: "sum", total: "sum" } },
|
|
33
|
+
* { label: "Average", aggregates: { quantity: "avg", total: "avg" } }
|
|
34
|
+
* ],
|
|
35
|
+
* labelProperty: "customer", // the labels go into the customer column
|
|
36
|
+
* rowHeight: 36
|
|
37
|
+
* };
|
|
38
|
+
*
|
|
39
|
+
* @since 1.1.94
|
|
40
|
+
* @category Aggregation
|
|
41
|
+
*/
|
|
42
|
+
export interface AggregateFooterOptionsIf<T = any> {
|
|
43
|
+
/** One footer row per entry. */
|
|
44
|
+
rows: AggregateFooterRowIf<T>[];
|
|
45
|
+
/** The column of the labels (property). Default: the first data column without an aggregate in that row. */
|
|
46
|
+
labelProperty?: string;
|
|
47
|
+
/** Height of the footer rows in px. Default 34 (the factory uses `defaultRowHeights.footer`). */
|
|
48
|
+
rowHeight?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Which rows are aggregated: `"filtered"` (default) – the rows the body shows (after `externalFilterFunction`;
|
|
51
|
+
* in a tree all leaves that pass the filter, expanded or not); `"all"` – all rows, the filter is ignored.
|
|
52
|
+
*/
|
|
53
|
+
source?: "filtered" | "all";
|
|
54
|
+
/** An own source of rows (instead of the body model's rows), e.g. the data of a server response. */
|
|
55
|
+
getRows?: (bodyModel: AreaModelIf) => ArrayLike<T>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The short and the full forms of a footer configuration:
|
|
59
|
+
* - `{ total: "sum" }` – one row ({@link AggregateSpecMap}),
|
|
60
|
+
* - `[{ label: "Sum", aggregates: … }, …]` – several rows ({@link AggregateFooterRowIf}),
|
|
61
|
+
* - `{ rows: […], labelProperty, rowHeight, source, getRows }` – everything ({@link AggregateFooterOptionsIf}).
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* const oneRow: AggregateFooterConfig<Order> = { quantity: "sum", total: "sum" };
|
|
65
|
+
* const twoRows: AggregateFooterConfig<Order> = [
|
|
66
|
+
* { label: "Sum", aggregates: { total: "sum" } },
|
|
67
|
+
* { label: "Max", aggregates: { total: "max" } }
|
|
68
|
+
* ];
|
|
69
|
+
*
|
|
70
|
+
* @since 1.1.94
|
|
71
|
+
* @category Aggregation
|
|
72
|
+
*/
|
|
73
|
+
export type AggregateFooterConfig<T = any> = AggregateSpecMap<T> | AggregateFooterRowIf<T>[] | AggregateFooterOptionsIf<T>;
|