@guiexpert/table 1.1.94 → 1.1.97
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 +16 -0
- package/css/main.css +299 -0
- package/css/table-calc.css +7 -0
- package/css/table-color-vars.css +58 -0
- package/css/table-fill-batch.css +58 -0
- package/css/table-master-detail.css +95 -0
- package/css/table-row-drag.css +81 -0
- package/index.cjs +37 -35
- package/index.d.ts +34 -0
- package/index.js +9749 -5000
- package/lib/table/action/action-id.type.d.ts +1 -1
- package/lib/table/aria/aria-ids.d.ts +7 -0
- package/lib/table/aria/aria-texts.d.ts +13 -0
- package/lib/table/aria/aria-texts.if.d.ts +101 -0
- package/lib/table/batch-edit/batch-edit-options.d.ts +21 -0
- package/lib/table/batch-edit/batch-edit-options.if.d.ts +20 -0
- package/lib/table/batch-edit/batch-edit-service.d.ts +104 -0
- package/lib/table/batch-edit/cell-change.if.d.ts +39 -0
- package/lib/table/batch-edit/cell-value-change.if.d.ts +18 -0
- package/lib/table/batch-edit/pending-cell-changes.d.ts +104 -0
- package/lib/table/calc/column-values.d.ts +55 -0
- package/lib/table/calc/show-values-as-internals.d.ts +4 -0
- package/lib/table/calc/show-values-as.d.ts +87 -0
- package/lib/table/calc/show-values-as.type.d.ts +55 -0
- package/lib/table/calc/value-getter-context.if.d.ts +35 -0
- package/lib/table/calc/value-getter.type.d.ts +36 -0
- package/lib/table/data/common/event/ge-batch-edit-event.d.ts +46 -0
- package/lib/table/data/common/event/ge-detail-event.d.ts +40 -0
- package/lib/table/data/common/event/ge-model-change-event.d.ts +27 -1
- package/lib/table/data/common/event/ge-row-drag-event.d.ts +89 -0
- package/lib/table/data/options/auto-restore-options.d.ts +2 -0
- package/lib/table/data/options/auto-restore-options.if.d.ts +6 -0
- package/lib/table/data/options/table-options.d.ts +8 -0
- package/lib/table/data/options/table-options.if.d.ts +62 -0
- package/lib/table/data/tablemodel/areamodel/abstract-area-model.d.ts +66 -0
- package/lib/table/data/tablemodel/areamodel/area-model-aggregate-footer.d.ts +5 -0
- package/lib/table/data/tablemodel/areamodel/area-model-array-of-arrays.d.ts +24 -0
- package/lib/table/data/tablemodel/areamodel/area-model-object-array.d.ts +70 -0
- package/lib/table/data/tablemodel/areamodel/area-model-pivot.d.ts +31 -0
- package/lib/table/data/tablemodel/areamodel/area-model-row-groups.d.ts +23 -1
- package/lib/table/data/tablemodel/areamodel/area-model-tree.d.ts +83 -0
- package/lib/table/data/tablemodel/areamodel/area-model.if.d.ts +55 -0
- package/lib/table/data/tablemodel/column/column-def.d.ts +31 -1
- package/lib/table/data/tablemodel/column/column-def.if.d.ts +86 -0
- package/lib/table/ele-scope.d.ts +14 -0
- package/lib/table/event-adapter.d.ts +10 -0
- package/lib/table/event-listener.if.d.ts +39 -0
- package/lib/table/factory/table-factory.d.ts +11 -0
- package/lib/table/fill/compute-fill-changes.d.ts +67 -0
- package/lib/table/fill/fill-direction.type.d.ts +7 -0
- package/lib/table/fill/fill-handle-options.d.ts +29 -0
- package/lib/table/fill/fill-handle-options.if.d.ts +35 -0
- package/lib/table/fill/fill-handle-service.d.ts +55 -0
- package/lib/table/fill/fill-series.d.ts +60 -0
- package/lib/table/fill/fill-value-context.if.d.ts +36 -0
- package/lib/table/formula/compile-formula.d.ts +86 -0
- package/lib/table/formula/evaluator.d.ts +19 -0
- package/lib/table/formula/formula-ast.if.d.ts +42 -0
- package/lib/table/formula/formula-error.d.ts +59 -0
- package/lib/table/formula/formula-options.if.d.ts +41 -0
- package/lib/table/formula/formula-values.d.ts +13 -0
- package/lib/table/formula/functions.d.ts +18 -0
- package/lib/table/formula/parser.d.ts +10 -0
- package/lib/table/formula/tokenizer.d.ts +22 -0
- package/lib/table/master-detail/detail-renderer.if.d.ts +99 -0
- package/lib/table/master-detail/master-detail-options.d.ts +30 -0
- package/lib/table/master-detail/master-detail-options.if.d.ts +60 -0
- package/lib/table/master-detail/master-detail-service.d.ts +223 -0
- package/lib/table/pivot/pivot-value.type.d.ts +13 -0
- package/lib/table/render-scope.d.ts +154 -0
- package/lib/table/renderer/edit/input-cell-renderer.d.ts +1 -1
- package/lib/table/renderer/edit/select-cell-renderer.d.ts +1 -1
- package/lib/table/row-drag/move-rows.d.ts +157 -0
- package/lib/table/row-drag/row-drag-options.d.ts +28 -0
- package/lib/table/row-drag/row-drag-options.if.d.ts +36 -0
- package/lib/table/row-drag/row-drag-service.d.ts +130 -0
- package/lib/table/row-drag/row-drop-target.if.d.ts +29 -0
- package/lib/table/row-layout/row-layout.d.ts +120 -0
- package/lib/table/service/excel-service.d.ts +6 -0
- package/lib/table/service/excel-service.if.d.ts +35 -0
- package/lib/table/state/table-state.if.d.ts +4 -1
- package/lib/table/state/table-state.service.d.ts +4 -0
- package/lib/table/table-api.d.ts +378 -2
- package/lib/table/table-scope.d.ts +185 -0
- package/package.json +1 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { FillValueContextIf } from './fill-value-context.if.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Options of the fill handle ({@link TableOptionsIf.fillHandle}): the small square at the bottom right corner of the
|
|
5
|
+
* selected range. Dragging it fills the neighbouring cells – copies values or continues series (1, 2, 3 …; dates;
|
|
6
|
+
* `Item 1` → `Item 2`). Needs a {@link SelectionModel} of the type `"range"`.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const selectionModel = new SelectionModel("range", "multi"); // one model – not a new one per call
|
|
10
|
+
* const tableOptions: TableOptionsIf = {
|
|
11
|
+
* ...new TableOptions(),
|
|
12
|
+
* getSelectionModel: () => selectionModel,
|
|
13
|
+
* fillHandle: { direction: "vertical" },
|
|
14
|
+
* };
|
|
15
|
+
*
|
|
16
|
+
* @category Fill handle & batch edit
|
|
17
|
+
* @since 1.1.96
|
|
18
|
+
*/
|
|
19
|
+
export interface FillHandleOptionsIf {
|
|
20
|
+
/** Allowed directions: `"vertical"` (down/up), `"horizontal"` (right/left) or `"both"` (default). */
|
|
21
|
+
direction: "vertical" | "horizontal" | "both";
|
|
22
|
+
/**
|
|
23
|
+
* Own fill function, called for every filled cell. Return the value – or `undefined` for the default
|
|
24
|
+
* (`ctx.defaultValue`).
|
|
25
|
+
*/
|
|
26
|
+
fillValue?: (ctx: FillValueContextIf) => unknown;
|
|
27
|
+
/** At most this many cells are filled at once (default 100,000); a bigger fill is refused and announced. */
|
|
28
|
+
maxCells: number;
|
|
29
|
+
/** Distance in px the pointer has to move before a fill starts (default 3). */
|
|
30
|
+
dragThresholdInPx: number;
|
|
31
|
+
/** Width in px of the zone at the edges of the body that scrolls automatically (default 40). */
|
|
32
|
+
autoScrollZoneInPx: number;
|
|
33
|
+
/** Maximum auto-scroll speed in px per animation frame (default 24). */
|
|
34
|
+
autoScrollMaxSpeedInPx: number;
|
|
35
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { TableScope } from '../table-scope.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The fill handle of one table (`tableScope.fillHandleService`): the square at the bottom right corner of the selected
|
|
5
|
+
* range, the pointer drag with preview and auto-scroll, Escape. The values come from {@link TableScope.fillRange}.
|
|
6
|
+
* Use {@link TableApi.fillRange}, {@link TableApi.isFilling} and {@link TableApi.cancelFill} instead.
|
|
7
|
+
*
|
|
8
|
+
* Pointer Events (mouse, touch, pen) only on the handle; its `pointerdown` is prevented, so the mouse handler of the
|
|
9
|
+
* table (range selection, column drag) never sees it.
|
|
10
|
+
*
|
|
11
|
+
* @category Internals
|
|
12
|
+
* @since 1.1.96
|
|
13
|
+
*/
|
|
14
|
+
export declare class FillHandleService {
|
|
15
|
+
private readonly tableScope;
|
|
16
|
+
private handle?;
|
|
17
|
+
private preview?;
|
|
18
|
+
private pending?;
|
|
19
|
+
private session?;
|
|
20
|
+
private sessionAbortController?;
|
|
21
|
+
private autoScrollFrame;
|
|
22
|
+
private suppressClick;
|
|
23
|
+
private shortcutsSet;
|
|
24
|
+
constructor(tableScope: TableScope);
|
|
25
|
+
/** true while the handle is dragged. */
|
|
26
|
+
isFilling(): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Places the handle at the bottom right corner of the last selected range (called after every render pass) – or
|
|
29
|
+
* hides it: no fill handle option, no range, the corner scrolled out, an open editor, a blocking overlay.
|
|
30
|
+
*/
|
|
31
|
+
update(): void;
|
|
32
|
+
/** The handle's position (the corner of the range) in host coordinates – `undefined` if it is not visible. */
|
|
33
|
+
private getCornerOf;
|
|
34
|
+
private ensureHandle;
|
|
35
|
+
private removeHandle;
|
|
36
|
+
/** Keyboard alternative on the grid: Ctrl/⌘+D, Ctrl/⌘+R (only directions the options allow). */
|
|
37
|
+
private updateHostShortcuts;
|
|
38
|
+
private isHandle;
|
|
39
|
+
private onPointerDown;
|
|
40
|
+
private listenToDocument;
|
|
41
|
+
private onPointerMove;
|
|
42
|
+
private onPointerUp;
|
|
43
|
+
private suppressNextClick;
|
|
44
|
+
private onClickCapture;
|
|
45
|
+
/** Target cell under the pointer → direction and count; preview; auto-scroll. */
|
|
46
|
+
private updateTarget;
|
|
47
|
+
private renderPreview;
|
|
48
|
+
private updateAutoScroll;
|
|
49
|
+
private autoScrollStep;
|
|
50
|
+
/** Cancels a running fill (Escape, `pointercancel`, {@link TableApi.cancelFill}): nothing is changed. */
|
|
51
|
+
cancel(): void;
|
|
52
|
+
private stopListening;
|
|
53
|
+
/** Releases everything (TableScope.destroy). */
|
|
54
|
+
destroy(): void;
|
|
55
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How {@link computeFillValues} continues the source values:
|
|
3
|
+
* - `"auto"`: the default rules – one number is copied, several numbers continue as a linear series, dates and texts
|
|
4
|
+
* with a trailing number count up, everything else repeats;
|
|
5
|
+
* - `"alternate"`: the other choice for numbers, dates and texts with a number (⌥/Alt or Ctrl while dragging the fill
|
|
6
|
+
* handle): one number counts up by 1, a series is copied;
|
|
7
|
+
* - `"copy"`: always repeat the source values (Ctrl/⌘+D, Ctrl/⌘+R).
|
|
8
|
+
*
|
|
9
|
+
* @category Fill handle & batch edit
|
|
10
|
+
* @since 1.1.96
|
|
11
|
+
*/
|
|
12
|
+
export type FillMode = "auto" | "alternate" | "copy";
|
|
13
|
+
/**
|
|
14
|
+
* Computes the values of a fill (fill handle, Ctrl/⌘+D): `count` values that continue `sourceValues` – forward
|
|
15
|
+
* (down/right) or backward (up/left: the value directly in front of the source first).
|
|
16
|
+
*
|
|
17
|
+
* | source | `"auto"` | `"alternate"` |
|
|
18
|
+
* |---|---|---|
|
|
19
|
+
* | one number `5` | 5, 5, 5 | 6, 7, 8 |
|
|
20
|
+
* | numbers `1, 2` | 3, 4, 5 (linear; uneven steps: best-fit line) | 1, 2, 1 |
|
|
21
|
+
* | date `2026-10-14` (`Date` or ISO text) | +1 day each; several dates a month apart: +1 month (month ends stay month ends) | copied |
|
|
22
|
+
* | text with a trailing number `Item 9` | `Item 10`, `Item 11` (leading zeros stay) | copied |
|
|
23
|
+
* | anything else | repeated: `A, B` → `A, B, A, B` | repeated |
|
|
24
|
+
*
|
|
25
|
+
* The type stays: a date text stays a text in the same format, a `Date` stays a `Date`, a number written as text
|
|
26
|
+
* stays a text. Decimal noise is rounded (0.1, 0.2 → 0.3, not 0.30000000000000004).
|
|
27
|
+
*
|
|
28
|
+
* @param {unknown[]} sourceValues - the values of one line (column or row) of the source range, in display order
|
|
29
|
+
* @param {number} count - the number of values to compute
|
|
30
|
+
* @param {boolean} [backward=false] - fill up/left: the values in front of the source, nearest first
|
|
31
|
+
* @param {FillMode} [mode="auto"] - see {@link FillMode}
|
|
32
|
+
* @returns {unknown[]} `count` values, nearest to the source first
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* computeFillValues([1, 2], 3); // [3, 4, 5]
|
|
36
|
+
* computeFillValues([5], 3); // [5, 5, 5]
|
|
37
|
+
* computeFillValues([5], 3, false, "alternate"); // [6, 7, 8]
|
|
38
|
+
* computeFillValues(["2026-10-14"], 2); // ["2026-10-15", "2026-10-16"]
|
|
39
|
+
* computeFillValues(["Item 9"], 2); // ["Item 10", "Item 11"]
|
|
40
|
+
* computeFillValues([1, 2], 2, true); // [0, -1] (filled upwards)
|
|
41
|
+
*
|
|
42
|
+
* @category Fill handle & batch edit
|
|
43
|
+
* @since 1.1.96
|
|
44
|
+
*/
|
|
45
|
+
export declare function computeFillValues(sourceValues: unknown[], count: number, backward?: boolean, mode?: FillMode): unknown[];
|
|
46
|
+
/**
|
|
47
|
+
* `true` if {@link computeFillValues} continues the values as a series, `false` if it repeats them.
|
|
48
|
+
*
|
|
49
|
+
* @param {unknown[]} sourceValues - the values of one line of the source range
|
|
50
|
+
* @param {FillMode} [mode="auto"] - see {@link FillMode}
|
|
51
|
+
* @returns {boolean} whether the fill is a series
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* isFillSeries([5]); // false – one number is copied
|
|
55
|
+
* isFillSeries([1, 2]); // true
|
|
56
|
+
*
|
|
57
|
+
* @category Fill handle & batch edit
|
|
58
|
+
* @since 1.1.96
|
|
59
|
+
*/
|
|
60
|
+
export declare function isFillSeries(sourceValues: unknown[], mode?: FillMode): boolean;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { FillDirection } from './fill-direction.type.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What an own fill function ({@link FillHandleOptionsIf.fillValue}) gets for each filled cell.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const weekdays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
8
|
+
* const fillValue = (ctx: FillValueContextIf) => {
|
|
9
|
+
* const last = ctx.sourceValues[ctx.sourceValues.length - 1];
|
|
10
|
+
* const i = weekdays.indexOf(String(last));
|
|
11
|
+
* return i < 0 || ctx.copy ? undefined : weekdays[(((i + ctx.step) % 7) + 7) % 7]; // undefined = default
|
|
12
|
+
* };
|
|
13
|
+
*
|
|
14
|
+
* @category Fill handle & batch edit
|
|
15
|
+
* @since 1.1.96
|
|
16
|
+
*/
|
|
17
|
+
export interface FillValueContextIf {
|
|
18
|
+
/** The values of the source line (the column when filling down/up, the row when filling right/left), as shown. */
|
|
19
|
+
sourceValues: unknown[];
|
|
20
|
+
/** 1, 2, 3 … after the source (down/right), −1, −2 … in front of it (up/left). */
|
|
21
|
+
step: number;
|
|
22
|
+
/** The direction of the fill. */
|
|
23
|
+
direction: FillDirection;
|
|
24
|
+
/** Display index of the filled cell's row. */
|
|
25
|
+
rowIndex: number;
|
|
26
|
+
/** Model index of the filled cell's column. */
|
|
27
|
+
columnIndex: number;
|
|
28
|
+
/** The property of the column (`""` without a column definition). */
|
|
29
|
+
property: string;
|
|
30
|
+
/** The row object of the filled cell (`undefined` for own area models without row objects). */
|
|
31
|
+
row: unknown;
|
|
32
|
+
/** The value the table would use (series or copy). */
|
|
33
|
+
defaultValue: unknown;
|
|
34
|
+
/** `true` if the values are copied (⌥/Alt on a series, Ctrl/⌘+D, Ctrl/⌘+R). */
|
|
35
|
+
copy: boolean;
|
|
36
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { FormulaNode } from './formula-ast.if.js';
|
|
2
|
+
import { FormulaError } from './formula-error.js';
|
|
3
|
+
import { FormulaOptionsIf, FormulaValue } from './formula-options.if.js';
|
|
4
|
+
import { ValueGetterContextIf } from '../calc/value-getter-context.if.js';
|
|
5
|
+
import { ValueGetter } from '../calc/value-getter.type.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The names of the built-in formula functions (upper case), e.g. for the help text of an own formula input.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* hint.textContent = `Functions: ${FORMULA_FUNCTION_NAMES.join(", ")}`;
|
|
12
|
+
*
|
|
13
|
+
* @since 1.1.95
|
|
14
|
+
* @category Calculated columns & formulas
|
|
15
|
+
*/
|
|
16
|
+
export declare const FORMULA_FUNCTION_NAMES: readonly string[];
|
|
17
|
+
/**
|
|
18
|
+
* A formula compiled once ({@link compileFormula}): the AST, the referenced fields and `evaluate()` for a row.
|
|
19
|
+
* A formula with a syntax error or an unknown function has `error` set; `evaluate()` then always returns it.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* const f = compileFormula("[price] * [quantity] * 1.19");
|
|
23
|
+
* f.fields; // ["price", "quantity"]
|
|
24
|
+
* f.evaluate({ price: 1299, quantity: 2 }); // 3091.62
|
|
25
|
+
*
|
|
26
|
+
* @since 1.1.95
|
|
27
|
+
* @category Calculated columns & formulas
|
|
28
|
+
*/
|
|
29
|
+
export interface CompiledFormula {
|
|
30
|
+
/** The formula text as given. */
|
|
31
|
+
readonly source: string;
|
|
32
|
+
/** The syntax tree; `undefined` if the formula has an error. */
|
|
33
|
+
readonly ast?: FormulaNode;
|
|
34
|
+
/** The error of the formula itself (`#ERROR!`, `#NAME?` for an unknown function); `undefined` if it is valid. */
|
|
35
|
+
readonly error?: FormulaError;
|
|
36
|
+
/** The referenced fields (`[price]` → `"price"`), each once, in order of appearance. */
|
|
37
|
+
readonly fields: readonly string[];
|
|
38
|
+
/** True if the formula uses a column aggregate (`SUM([total])` …). */
|
|
39
|
+
readonly usesAggregates: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Evaluates the formula for a row. Never throws: errors are returned as {@link FormulaError}.
|
|
42
|
+
* Without a context, column aggregates (`SUM([x])`) return `#VALUE!`.
|
|
43
|
+
*/
|
|
44
|
+
evaluate(row: unknown, context?: ValueGetterContextIf): FormulaValue;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Compiles a formula once – no `eval`, no `new Function` (works with a strict Content-Security-Policy, user input
|
|
48
|
+
* cannot run code). The result evaluates rows; never throws.
|
|
49
|
+
*
|
|
50
|
+
* Syntax (details: instructions.md „Formulas“):
|
|
51
|
+
* - fields in brackets: `[price]`, `[address.city]`, `[Unit price]` – own properties of the row or calculated columns
|
|
52
|
+
* - numbers `12`, `1.5`, `1e3`, texts `"a"` / `'a'`, `TRUE`, `FALSE`
|
|
53
|
+
* - operators: `+ - * / ^` (power), `&` (text), `= <> < <= > >=`; `-2^2 = -4`
|
|
54
|
+
* - functions: `ROUND(x, 2)`, `IF(c, a, b)`, `IFERROR`, `AND`, `OR`, `NOT`, `ABS`, `MOD`, `SQRT`, `POWER`, `FLOOR`,
|
|
55
|
+
* `CEILING`, `CONCAT`, `LEN`, `UPPER`, `LOWER`, `TRIM`, `ISBLANK`; `SUM AVG MIN MAX COUNT` – with one field the
|
|
56
|
+
* aggregate of the column (`[total] / SUM([total])`), otherwise of the arguments
|
|
57
|
+
*
|
|
58
|
+
* @param {string} source - the formula, e.g. `"[price] * [quantity] * 1.19"` (a leading `=` is ignored)
|
|
59
|
+
* @param {FormulaOptionsIf} [options] - own functions
|
|
60
|
+
* @returns {CompiledFormula} the compiled formula
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* const gross = compileFormula("ROUND([price] * [quantity] * 1.19, 2)");
|
|
64
|
+
* gross.evaluate({ price: 25, quantity: 10 }); // 297.5
|
|
65
|
+
* compileFormula("[price] / 0").evaluate({ price: 1 }); // FormulaError #DIV/0!
|
|
66
|
+
* compileFormula("[price] *").error?.code; // "#ERROR!"
|
|
67
|
+
*
|
|
68
|
+
* @since 1.1.95
|
|
69
|
+
* @category Calculated columns & formulas
|
|
70
|
+
*/
|
|
71
|
+
export declare function compileFormula(source: string, options?: FormulaOptionsIf): CompiledFormula;
|
|
72
|
+
/**
|
|
73
|
+
* The {@link ValueGetter} of a formula – what `ColumnDef.create({ formula })` sets as `valueGetter`.
|
|
74
|
+
*
|
|
75
|
+
* @param {CompiledFormula | string} formula - a compiled formula or its text
|
|
76
|
+
* @param {FormulaOptionsIf} [options] - own functions (only with a text)
|
|
77
|
+
* @returns {ValueGetter} the getter
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* const column = ColumnDef.create({ property: "gross", headerLabel: "Gross" });
|
|
81
|
+
* column.valueGetter = createFormulaValueGetter("[net] * 1.19");
|
|
82
|
+
*
|
|
83
|
+
* @since 1.1.95
|
|
84
|
+
* @category Calculated columns & formulas
|
|
85
|
+
*/
|
|
86
|
+
export declare function createFormulaValueGetter(formula: CompiledFormula | string, options?: FormulaOptionsIf): ValueGetter;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { FormulaNode } from './formula-ast.if.js';
|
|
2
|
+
import { FormulaFunction, FormulaValue } from './formula-options.if.js';
|
|
3
|
+
import { ValueGetterContextIf } from '../calc/value-getter-context.if.js';
|
|
4
|
+
|
|
5
|
+
type Evaluate = (row: unknown, context: ValueGetterContextIf | undefined) => FormulaValue;
|
|
6
|
+
/** Reads a field: own properties only (no prototype chain), path segments separated by ".". */
|
|
7
|
+
export declare function readField(row: unknown, name: string, context?: ValueGetterContextIf, path?: string[]): FormulaValue;
|
|
8
|
+
/** Compiles an AST into closures. Never throws at evaluation time (see {@link FormulaEvaluator.evaluate}). */
|
|
9
|
+
export declare class FormulaEvaluator {
|
|
10
|
+
private readonly functions;
|
|
11
|
+
constructor(functions: Map<string, FormulaFunction>);
|
|
12
|
+
/** The evaluation function of a (checked) AST. */
|
|
13
|
+
compile(node: FormulaNode): Evaluate;
|
|
14
|
+
/** Evaluates an AST once (compiles it each time – for single use). */
|
|
15
|
+
evaluate(node: FormulaNode, row: unknown, context?: ValueGetterContextIf): FormulaValue;
|
|
16
|
+
private node;
|
|
17
|
+
private call;
|
|
18
|
+
}
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A node of a compiled formula (abstract syntax tree). Read-only; the evaluator walks it – no code is generated
|
|
3
|
+
* (no `eval`, no `new Function`).
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* const ast = compileFormula("[price] * 2").ast;
|
|
7
|
+
* // { kind: "binary", op: "*", left: { kind: "field", name: "price" }, right: { kind: "literal", value: 2 } }
|
|
8
|
+
*
|
|
9
|
+
* @since 1.1.95
|
|
10
|
+
* @category Calculated columns & formulas
|
|
11
|
+
*/
|
|
12
|
+
export type FormulaNode = {
|
|
13
|
+
readonly kind: "literal";
|
|
14
|
+
readonly value: number | string | boolean;
|
|
15
|
+
} | {
|
|
16
|
+
readonly kind: "field";
|
|
17
|
+
readonly name: string;
|
|
18
|
+
} | {
|
|
19
|
+
readonly kind: "unary";
|
|
20
|
+
readonly op: "-" | "+";
|
|
21
|
+
readonly operand: FormulaNode;
|
|
22
|
+
} | {
|
|
23
|
+
readonly kind: "binary";
|
|
24
|
+
readonly op: FormulaBinaryOperator;
|
|
25
|
+
readonly left: FormulaNode;
|
|
26
|
+
readonly right: FormulaNode;
|
|
27
|
+
} | {
|
|
28
|
+
readonly kind: "call";
|
|
29
|
+
readonly name: string;
|
|
30
|
+
readonly args: readonly FormulaNode[];
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* The binary operators of a formula: arithmetic (`+ - * / ^`), text concatenation (`&`) and comparisons
|
|
34
|
+
* (`=`/`==`, `<>`/`!=`, `<`, `<=`, `>`, `>=`; normalized to `=` and `<>`).
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* const op: FormulaBinaryOperator = "&"; // "Total: " & [total]
|
|
38
|
+
*
|
|
39
|
+
* @since 1.1.95
|
|
40
|
+
* @category Calculated columns & formulas
|
|
41
|
+
*/
|
|
42
|
+
export type FormulaBinaryOperator = "+" | "-" | "*" | "/" | "^" | "&" | "=" | "<>" | "<" | "<=" | ">" | ">=";
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The error codes of a formula – like in a spreadsheet:
|
|
3
|
+
*
|
|
4
|
+
* | Code | Meaning |
|
|
5
|
+
* |---|---|
|
|
6
|
+
* | `#ERROR!` | syntax error (the whole column shows it) |
|
|
7
|
+
* | `#NAME?` | unknown field or function |
|
|
8
|
+
* | `#VALUE!` | wrong type, e.g. `"abc" * 2` |
|
|
9
|
+
* | `#DIV/0!` | division by zero |
|
|
10
|
+
* | `#NUM!` | the result is not a finite number (`SQRT(-1)`, overflow) |
|
|
11
|
+
* | `#CIRC!` | circular reference between calculated columns |
|
|
12
|
+
*
|
|
13
|
+
* @since 1.1.95
|
|
14
|
+
* @category Calculated columns & formulas
|
|
15
|
+
*/
|
|
16
|
+
export type FormulaErrorCode = "#ERROR!" | "#NAME?" | "#VALUE!" | "#DIV/0!" | "#NUM!" | "#CIRC!";
|
|
17
|
+
/**
|
|
18
|
+
* An error value of a formula. Formulas never throw: a cell shows the {@link FormulaErrorCode} (`toString()`), the
|
|
19
|
+
* table adds the CSS class `ge-table-cell-error` and shows the message as tooltip. Errors propagate through
|
|
20
|
+
* operators and functions (`[a] / 0 + 1` → `#DIV/0!`), `IFERROR(x, fallback)` catches them, aggregators skip them.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const result = compileFormula("[price] / [quantity]").evaluate({ price: 10, quantity: 0 });
|
|
24
|
+
* if (isFormulaError(result)) {
|
|
25
|
+
* console.log(result.code, result.message); // "#DIV/0!" "Division by zero"
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* @since 1.1.95
|
|
29
|
+
* @category Calculated columns & formulas
|
|
30
|
+
*/
|
|
31
|
+
export declare class FormulaError {
|
|
32
|
+
readonly code: FormulaErrorCode;
|
|
33
|
+
readonly message: string;
|
|
34
|
+
readonly position?: number | undefined;
|
|
35
|
+
/** Marker (works across copies of the core): {@link isFormulaError}. */
|
|
36
|
+
readonly type = "FormulaError";
|
|
37
|
+
/**
|
|
38
|
+
* @param {FormulaErrorCode} code - the code shown in the cell
|
|
39
|
+
* @param {string} message - a short English explanation (tooltip); never contains HTML
|
|
40
|
+
* @param {number} [position] - for syntax errors: the position in the formula text (0-based)
|
|
41
|
+
*/
|
|
42
|
+
constructor(code: FormulaErrorCode, message: string, position?: number | undefined);
|
|
43
|
+
/** The code, e.g. `"#DIV/0!"` – what a cell shows. */
|
|
44
|
+
toString(): string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* True for an error value of a formula ({@link FormulaError}) – also for one created by another copy of the core.
|
|
48
|
+
*
|
|
49
|
+
* @param {unknown} value - any value
|
|
50
|
+
* @returns {boolean} whether it is a formula error
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* const value = tableApi.getRowValue(row, "margin");
|
|
54
|
+
* if (!isFormulaError(value)) console.log(value);
|
|
55
|
+
*
|
|
56
|
+
* @since 1.1.95
|
|
57
|
+
* @category Calculated columns & formulas
|
|
58
|
+
*/
|
|
59
|
+
export declare function isFormulaError(value: unknown): value is FormulaError;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { FormulaError } from './formula-error.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A value inside a formula: a number, a text, a boolean, blank (`null`) or an error.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const fn: FormulaFunction = (x: FormulaValue) => typeof x === "number" ? x * 1.19 : x;
|
|
8
|
+
*
|
|
9
|
+
* @since 1.1.95
|
|
10
|
+
* @category Calculated columns & formulas
|
|
11
|
+
*/
|
|
12
|
+
export type FormulaValue = number | string | boolean | null | FormulaError;
|
|
13
|
+
/**
|
|
14
|
+
* An own function for formulas. It gets the evaluated arguments (never an error – an error argument is returned
|
|
15
|
+
* without calling the function) and returns a number, text, boolean or `null`. Anything else, an exception or
|
|
16
|
+
* `NaN` becomes `#VALUE!` resp. `#NUM!`.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* const VAT: FormulaFunction = (net) => Number(net) * 0.19;
|
|
20
|
+
* compileFormula("VAT([price])", { functions: { VAT } });
|
|
21
|
+
*
|
|
22
|
+
* @since 1.1.95
|
|
23
|
+
* @category Calculated columns & formulas
|
|
24
|
+
*/
|
|
25
|
+
export type FormulaFunction = (...args: FormulaValue[]) => unknown;
|
|
26
|
+
/**
|
|
27
|
+
* Options of {@link compileFormula}.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* const options: FormulaOptionsIf = {
|
|
31
|
+
* functions: { NET: (gross) => Number(gross) / 1.19 }
|
|
32
|
+
* };
|
|
33
|
+
* ColumnDef.create({ property: "net", headerLabel: "Net", formula: "NET([gross])", formulaOptions: options });
|
|
34
|
+
*
|
|
35
|
+
* @since 1.1.95
|
|
36
|
+
* @category Calculated columns & formulas
|
|
37
|
+
*/
|
|
38
|
+
export interface FormulaOptionsIf {
|
|
39
|
+
/** Own functions by name (case-insensitive). They may replace built-in ones. */
|
|
40
|
+
functions?: Record<string, FormulaFunction>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FormulaError } from './formula-error.js';
|
|
2
|
+
import { FormulaValue } from './formula-options.if.js';
|
|
3
|
+
|
|
4
|
+
export declare function isBlank(v: FormulaValue): boolean;
|
|
5
|
+
/** A value from the data (a field) as formula value: Date → ms, function/object → #VALUE!. */
|
|
6
|
+
export declare function fromData(v: unknown, name: string, trusted?: boolean): FormulaValue;
|
|
7
|
+
export declare function toNumber(v: FormulaValue): number | FormulaError;
|
|
8
|
+
export declare function toText(v: FormulaValue): string | FormulaError;
|
|
9
|
+
export declare function toBoolean(v: FormulaValue): boolean | FormulaError;
|
|
10
|
+
/** A number result: NaN / ±Infinity → #NUM!, -0 → 0. */
|
|
11
|
+
export declare function numberResult(n: number): number | FormulaError;
|
|
12
|
+
/** Compares two values: numbers numerically, texts case-insensitively, blank = 0 = "" = FALSE. */
|
|
13
|
+
export declare function compareValues(a: FormulaValue, b: FormulaValue): number | FormulaError;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { FormulaValue } from './formula-options.if.js';
|
|
2
|
+
|
|
3
|
+
/** A built-in function with eagerly evaluated arguments (errors are returned before the call). */
|
|
4
|
+
export interface BuiltInFunction {
|
|
5
|
+
readonly min: number;
|
|
6
|
+
readonly max: number;
|
|
7
|
+
readonly fn: (args: FormulaValue[]) => FormulaValue;
|
|
8
|
+
}
|
|
9
|
+
/** Functions with lazily evaluated arguments – handled by the evaluator itself. */
|
|
10
|
+
export declare const LAZY_FUNCTIONS: Record<string, {
|
|
11
|
+
min: number;
|
|
12
|
+
max: number;
|
|
13
|
+
}>;
|
|
14
|
+
/** SUM, AVG … with exactly one field reference: an aggregate over the (filtered) rows of the table. */
|
|
15
|
+
export declare const AGGREGATE_FUNCTIONS: Record<string, "sum" | "avg" | "min" | "max" | "count">;
|
|
16
|
+
/** Rounds half away from zero (like Excel) without binary artefacts: ROUND(1.005, 2) = 1.01. */
|
|
17
|
+
export declare function roundHalfAwayFromZero(x: number, digits: number): number;
|
|
18
|
+
export declare const BUILT_IN_FUNCTIONS: Record<string, BuiltInFunction>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { FormulaNode } from './formula-ast.if.js';
|
|
2
|
+
|
|
3
|
+
/** The maximum length of a formula (characters). */
|
|
4
|
+
export declare const MAX_FORMULA_LENGTH = 10000;
|
|
5
|
+
/** The maximum nesting depth (parentheses, function calls, unary operators). */
|
|
6
|
+
export declare const MAX_FORMULA_DEPTH = 64;
|
|
7
|
+
/** The maximum number of binary operators (a long chain is a deep tree, too). */
|
|
8
|
+
export declare const MAX_FORMULA_OPERATORS = 1000;
|
|
9
|
+
/** Parses a formula into an AST. Throws a {@link FormulaSyntaxError}. */
|
|
10
|
+
export declare function parseFormula(source: string): FormulaNode;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { FormulaError } from './formula-error.js';
|
|
2
|
+
|
|
3
|
+
/** The kinds of tokens of a formula. */
|
|
4
|
+
export type FormulaTokenKind = "number" | "string" | "field" | "ident" | "op" | "(" | ")" | "," | "eof";
|
|
5
|
+
/** One token: kind, text (number/string already decoded) and position in the source. */
|
|
6
|
+
export interface FormulaToken {
|
|
7
|
+
readonly kind: FormulaTokenKind;
|
|
8
|
+
readonly text: string;
|
|
9
|
+
readonly value?: number;
|
|
10
|
+
readonly pos: number;
|
|
11
|
+
}
|
|
12
|
+
/** Thrown by tokenizer and parser, caught by `compileFormula` – never leaves the formula module. */
|
|
13
|
+
export declare class FormulaSyntaxError extends Error {
|
|
14
|
+
readonly pos: number;
|
|
15
|
+
constructor(message: string, pos: number);
|
|
16
|
+
toFormulaError(): FormulaError;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Splits a formula into tokens. Whitespace is ignored. Throws a {@link FormulaSyntaxError} for an unknown character,
|
|
20
|
+
* an unterminated string or field reference.
|
|
21
|
+
*/
|
|
22
|
+
export declare function tokenize(source: string): FormulaToken[];
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { TableApi } from '../table-api.js';
|
|
2
|
+
import { TableModelIf } from '../data/tablemodel/table-model.if.js';
|
|
3
|
+
import { TableOptionsIf } from '../data/options/table-options.if.js';
|
|
4
|
+
import { EventListenerIf } from '../event-listener.if.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* What {@link DetailRenderContextIf.createTable} needs for a nested table in a detail row.
|
|
8
|
+
*
|
|
9
|
+
* @category Master/detail
|
|
10
|
+
* @since 1.1.97
|
|
11
|
+
*/
|
|
12
|
+
export interface DetailTableParamsIf {
|
|
13
|
+
/** The model of the nested table, e.g. `TableFactory.createTableModel({ rows: order.items, properties: [...] })`. */
|
|
14
|
+
tableModel: TableModelIf;
|
|
15
|
+
/** Its options (default: `new TableOptions()`). */
|
|
16
|
+
tableOptions?: TableOptionsIf;
|
|
17
|
+
/** Its event listener (default: none). */
|
|
18
|
+
eventListener?: EventListenerIf;
|
|
19
|
+
/**
|
|
20
|
+
* The height of the nested table: px or a CSS length. Default: `"100%"` with a fixed
|
|
21
|
+
* {@link MasterDetailOptionsIf.detailHeight}, otherwise {@link MasterDetailOptionsIf.estimatedDetailHeight} px.
|
|
22
|
+
*/
|
|
23
|
+
height?: number | string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The context a {@link DetailRendererIf} gets: the master row, the outer table and helpers whose resources are released
|
|
27
|
+
* together with the detail row.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* const renderDetail: DetailRenderFnType<Order> = (container, order, ctx) => {
|
|
31
|
+
* const button = document.createElement("button");
|
|
32
|
+
* button.textContent = `Open order ${order.id}`;
|
|
33
|
+
* button.addEventListener("click", () => ctx.tableApi.announce(`Order ${order.id}`), { signal: ctx.signal });
|
|
34
|
+
* container.appendChild(button);
|
|
35
|
+
* };
|
|
36
|
+
*
|
|
37
|
+
* @category Master/detail
|
|
38
|
+
* @since 1.1.97
|
|
39
|
+
*/
|
|
40
|
+
export interface DetailRenderContextIf<T = any> {
|
|
41
|
+
/** The master row (the row object of the body model). */
|
|
42
|
+
readonly row: T;
|
|
43
|
+
/** The display index of the master row when the detail was created (it changes with sorting and filtering). */
|
|
44
|
+
readonly rowIndex: number;
|
|
45
|
+
/** The API of the outer (master) table. */
|
|
46
|
+
readonly tableApi: TableApi;
|
|
47
|
+
/**
|
|
48
|
+
* `true` if the content of this detail row existed before and was destroyed (too many rendered details, see
|
|
49
|
+
* {@link MasterDetailOptionsIf.maxRenderedDetails}, or {@link TableApi.refreshDetail}) – state that lives only in the
|
|
50
|
+
* DOM (inputs, scroll positions) is gone then.
|
|
51
|
+
*/
|
|
52
|
+
readonly recreated: boolean;
|
|
53
|
+
/** Aborted when the detail row is destroyed – pass it to `addEventListener(…, { signal })`. */
|
|
54
|
+
readonly signal: AbortSignal;
|
|
55
|
+
/**
|
|
56
|
+
* Creates a GuiExpert table inside the detail row; it is destroyed (`TableScope.destroy()`) together with the detail
|
|
57
|
+
* row. The CSS variables of the page (light/dark theme) apply to it as well.
|
|
58
|
+
*
|
|
59
|
+
* @param {DetailTableParamsIf} params - model, options, listener, height
|
|
60
|
+
* @returns {TableApi} the API of the nested table
|
|
61
|
+
*/
|
|
62
|
+
createTable(params: DetailTableParamsIf): TableApi;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Renders the content of a detail row – a function (see {@link DetailRendererIf}). It may return a cleanup function,
|
|
66
|
+
* called when the detail row is destroyed (collapsed, too many rendered details, new table model).
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* const renderDetail: DetailRenderFnType<Order> = (container, order) => {
|
|
70
|
+
* container.textContent = `${order.items.length} items, ordered on ${order.date}`; // textContent: no HTML injection
|
|
71
|
+
* };
|
|
72
|
+
*
|
|
73
|
+
* @category Master/detail
|
|
74
|
+
* @since 1.1.97
|
|
75
|
+
*/
|
|
76
|
+
export type DetailRenderFnType<T = any> = (container: HTMLDivElement, row: T, context: DetailRenderContextIf<T>) => (() => void) | void;
|
|
77
|
+
/**
|
|
78
|
+
* Renders the content of a detail row – the object form of {@link DetailRenderFnType}, e.g. the
|
|
79
|
+
* `DetailComponentRendererWrapper` of the framework wrappers (React, Vue, Svelte …).
|
|
80
|
+
*
|
|
81
|
+
* The content is created once, when the detail row becomes visible for the first time, and lives until the row is
|
|
82
|
+
* collapsed – scrolling only hides and shows it (inputs, selections and scroll positions stay).
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* class OrderNotesRenderer implements DetailRendererIf<Order> {
|
|
86
|
+
* render(container: HTMLDivElement, order: Order): () => void {
|
|
87
|
+
* const textarea = document.createElement("textarea");
|
|
88
|
+
* textarea.value = order.notes ?? "";
|
|
89
|
+
* container.appendChild(textarea);
|
|
90
|
+
* return () => { order.notes = textarea.value; }; // cleanup: keep the text
|
|
91
|
+
* }
|
|
92
|
+
* }
|
|
93
|
+
*
|
|
94
|
+
* @category Master/detail
|
|
95
|
+
* @since 1.1.97
|
|
96
|
+
*/
|
|
97
|
+
export interface DetailRendererIf<T = any> {
|
|
98
|
+
render(container: HTMLDivElement, row: T, context: DetailRenderContextIf<T>): (() => void) | void;
|
|
99
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { DetailHeightType, MasterDetailOptionsIf } from './master-detail-options.if.js';
|
|
2
|
+
import { DetailRendererIf, DetailRenderFnType } from './detail-renderer.if.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The defaults of {@link MasterDetailOptionsIf}.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const options = MasterDetailOptions.create({ detailRenderer: (el, row) => { el.textContent = String(row.id); } });
|
|
9
|
+
* options.detailHeight; // "auto"
|
|
10
|
+
* options.maxRenderedDetails; // 50
|
|
11
|
+
*
|
|
12
|
+
* @category Master/detail
|
|
13
|
+
* @since 1.1.97
|
|
14
|
+
*/
|
|
15
|
+
export declare class MasterDetailOptions<T = any> implements MasterDetailOptionsIf<T> {
|
|
16
|
+
detailRenderer: DetailRendererIf<T> | DetailRenderFnType<T>;
|
|
17
|
+
isMaster?: (row: T, rowIndex: number) => boolean;
|
|
18
|
+
detailHeight: DetailHeightType | ((row: T, rowIndex: number) => DetailHeightType);
|
|
19
|
+
estimatedDetailHeight: number;
|
|
20
|
+
toggleColumn?: number | string;
|
|
21
|
+
maxRenderedDetails: number;
|
|
22
|
+
getRowId?: (row: T) => string | number;
|
|
23
|
+
/**
|
|
24
|
+
* The defaults, overridden by the given values (`undefined` keeps the default).
|
|
25
|
+
*
|
|
26
|
+
* @param {Partial<MasterDetailOptionsIf>} [options] - e.g. `tableOptions.masterDetail`
|
|
27
|
+
* @returns {MasterDetailOptionsIf} complete options
|
|
28
|
+
*/
|
|
29
|
+
static create<T = any>(options?: Partial<MasterDetailOptionsIf<T>>): MasterDetailOptionsIf<T>;
|
|
30
|
+
}
|