@svgrid/enterprise 3.0.1 → 3.0.2
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/dist/SvFormulaBar.svelte +606 -0
- package/dist/SvFormulaBar.svelte.d.ts +62 -0
- package/dist/SvPivotDesigner.svelte +65 -14
- package/dist/SvRibbonIcon.svelte +46 -0
- package/dist/SvRibbonIcon.svelte.d.ts +17 -0
- package/dist/SvSchemaChart.svelte +4 -2
- package/dist/SvSheet.svelte +4114 -0
- package/dist/SvSheet.svelte.d.ts +142 -0
- package/dist/SvSheetComment.svelte +98 -0
- package/dist/SvSheetComment.svelte.d.ts +22 -0
- package/dist/SvSheetConditionalFormat.svelte +162 -0
- package/dist/SvSheetConditionalFormat.svelte.d.ts +14 -0
- package/dist/SvSheetDataValidation.svelte +249 -0
- package/dist/SvSheetDataValidation.svelte.d.ts +14 -0
- package/dist/SvSheetFilterMenu.svelte +217 -0
- package/dist/SvSheetFilterMenu.svelte.d.ts +17 -0
- package/dist/SvSheetFindReplace.svelte +132 -0
- package/dist/SvSheetFindReplace.svelte.d.ts +10 -0
- package/dist/SvSheetFormatCells.svelte +421 -0
- package/dist/SvSheetFormatCells.svelte.d.ts +17 -0
- package/dist/SvSheetGoalSeek.svelte +144 -0
- package/dist/SvSheetGoalSeek.svelte.d.ts +12 -0
- package/dist/SvSheetInsertFunction.svelte +147 -0
- package/dist/SvSheetInsertFunction.svelte.d.ts +8 -0
- package/dist/SvSheetListPicker.svelte +89 -0
- package/dist/SvSheetListPicker.svelte.d.ts +16 -0
- package/dist/SvSheetManageRules.svelte +185 -0
- package/dist/SvSheetManageRules.svelte.d.ts +16 -0
- package/dist/SvSheetNameManager.svelte +225 -0
- package/dist/SvSheetNameManager.svelte.d.ts +11 -0
- package/dist/SvSheetPasteSpecial.svelte +105 -0
- package/dist/SvSheetPasteSpecial.svelte.d.ts +12 -0
- package/dist/SvSheetRemoveDuplicates.svelte +149 -0
- package/dist/SvSheetRemoveDuplicates.svelte.d.ts +12 -0
- package/dist/SvSheetRibbon.svelte +1201 -0
- package/dist/SvSheetRibbon.svelte.d.ts +34 -0
- package/dist/SvSheetSizeDialog.svelte +58 -0
- package/dist/SvSheetSizeDialog.svelte.d.ts +11 -0
- package/dist/SvSheetTabs.svelte +441 -0
- package/dist/SvSheetTabs.svelte.d.ts +36 -0
- package/dist/SvSheetTabs.test-harness.svelte.d.ts +17 -0
- package/dist/SvSheetTabs.test-harness.svelte.js +22 -0
- package/dist/SvSheetTextToColumns.svelte +172 -0
- package/dist/SvSheetTextToColumns.svelte.d.ts +13 -0
- package/dist/SvSheetValidationAlert.svelte +83 -0
- package/dist/SvSheetValidationAlert.svelte.d.ts +15 -0
- package/dist/cdn/svgrid-enterprise.svelte-external.js +18997 -5396
- package/dist/designer/assets/GridMenus-BlsvL_aU.js +8 -0
- package/dist/designer/assets/SvChartMenu-BYO9cYdy.css +1 -0
- package/dist/designer/assets/SvChartMenu-D9hEeHar.js +1 -0
- package/dist/designer/assets/SvDateRangeInput-BBRzrQeg.js +1 -0
- package/dist/designer/assets/SvDateTimePicker-DARvKUL2.js +1 -0
- package/dist/designer/assets/SvGridCellEditor-C7uQXYds.js +4 -0
- package/dist/designer/assets/SvGridChart-B8j1SSy3.js +3 -0
- package/dist/designer/assets/SvGridChart-BgLuPO7I.css +1 -0
- package/dist/designer/assets/SvGridChartBuilder-08YWWeqY.css +1 -0
- package/dist/designer/assets/SvGridChartBuilder-kvyIU_Rl.js +1 -0
- package/dist/designer/assets/SvGridChartPanel-4Pc8y5yO.css +1 -0
- package/dist/designer/assets/SvGridChartPanel-pT6ITc2P.js +4 -0
- package/dist/designer/assets/{SvGridChartView-DUc-1RUf.css → SvGridChartView-7msfnWZT.css} +1 -1
- package/dist/designer/assets/SvGridChartView-bg281UA1.js +1 -0
- package/dist/designer/assets/SvListBox-BDS66lmZ.js +110 -0
- package/dist/designer/assets/SvMenuList-DJ47ba1h.css +1 -0
- package/dist/designer/assets/SvMenuList-Dgq2D5VN.js +1 -0
- package/dist/designer/assets/SvTabs-DKmwb5p1.css +1 -0
- package/dist/designer/assets/SvTabs-UFCMTHXS.js +1 -0
- package/dist/designer/assets/chart-cIPUsbM5.js +1 -0
- package/dist/designer/assets/chart-export-B6bkQ3X8.js +11 -0
- package/dist/designer/assets/chart-export-pdf-BbA28-gt.js +11 -0
- package/dist/designer/assets/chart-panel-messages-DtTxpBUU.js +1 -0
- package/dist/designer/assets/chart-summary-Czu_5epb.js +1 -0
- package/dist/designer/assets/chart-trend-D5I5VEe8.js +1 -0
- package/dist/designer/assets/chart-validate-BMqM-qkF.js +1 -0
- package/dist/designer/assets/column-resize-BYDzR1Ve.js +1 -0
- package/dist/designer/assets/date-format-B8JbEtIG.js +1 -0
- package/dist/designer/assets/date-format-DKS-z0xS.css +1 -0
- package/dist/designer/assets/editor-contract-Bpoa7A5g.js +1 -0
- package/dist/designer/assets/focus-trap-CAreAhlU.js +1 -0
- package/dist/designer/assets/index-CMFgAoVV.css +1 -0
- package/dist/designer/assets/{index-C_a9Cj8Y.js → index-DwU7CAMm.js} +291 -290
- package/dist/designer/assets/list-option-BP_dWS66.js +1 -0
- package/dist/designer/assets/row-resize-DLktcqUK.js +1 -0
- package/dist/designer/assets/src-CITpcm6b.js +1 -0
- package/dist/designer/assets/src-EdI3Xqds.js +58 -0
- package/dist/designer/index.html +23 -14
- package/dist/export-pdf.d.ts +89 -0
- package/dist/export-pdf.js +130 -0
- package/dist/export.js +17 -10
- package/dist/index.d.ts +64 -1
- package/dist/index.js +68 -1
- package/dist/install.js +5 -0
- package/dist/node/studio.js +64 -2
- package/dist/pivot-chart.d.ts +25 -1
- package/dist/pivot-chart.js +25 -69
- package/dist/sheet/address.d.ts +43 -0
- package/dist/sheet/address.js +81 -0
- package/dist/sheet/ast.d.ts +94 -0
- package/dist/sheet/ast.js +56 -0
- package/dist/sheet/auto-filter.d.ts +66 -0
- package/dist/sheet/auto-filter.js +147 -0
- package/dist/sheet/autocomplete.d.ts +42 -0
- package/dist/sheet/autocomplete.js +162 -0
- package/dist/sheet/coerce.d.ts +31 -0
- package/dist/sheet/coerce.js +122 -0
- package/dist/sheet/commands.d.ts +89 -0
- package/dist/sheet/commands.js +230 -0
- package/dist/sheet/comments.d.ts +29 -0
- package/dist/sheet/comments.js +73 -0
- package/dist/sheet/conditional-formats.d.ts +144 -0
- package/dist/sheet/conditional-formats.js +316 -0
- package/dist/sheet/deps.d.ts +60 -0
- package/dist/sheet/deps.js +199 -0
- package/dist/sheet/document.d.ts +150 -0
- package/dist/sheet/document.js +264 -0
- package/dist/sheet/edit-keys.d.ts +14 -0
- package/dist/sheet/edit-keys.js +40 -0
- package/dist/sheet/entry.d.ts +31 -0
- package/dist/sheet/entry.js +76 -0
- package/dist/sheet/evaluate.d.ts +61 -0
- package/dist/sheet/evaluate.js +337 -0
- package/dist/sheet/find-replace.d.ts +62 -0
- package/dist/sheet/find-replace.js +152 -0
- package/dist/sheet/format-store.d.ts +99 -0
- package/dist/sheet/format-store.js +233 -0
- package/dist/sheet/freeze.d.ts +54 -0
- package/dist/sheet/freeze.js +58 -0
- package/dist/sheet/function-catalog.d.ts +10 -0
- package/dist/sheet/function-catalog.js +94 -0
- package/dist/sheet/functions.d.ts +28 -0
- package/dist/sheet/functions.js +362 -0
- package/dist/sheet/goal-seek.d.ts +73 -0
- package/dist/sheet/goal-seek.js +153 -0
- package/dist/sheet/index.d.ts +53 -0
- package/dist/sheet/index.js +53 -0
- package/dist/sheet/merges.d.ts +71 -0
- package/dist/sheet/merges.js +137 -0
- package/dist/sheet/names.d.ts +31 -0
- package/dist/sheet/names.js +87 -0
- package/dist/sheet/navigate.d.ts +52 -0
- package/dist/sheet/navigate.js +135 -0
- package/dist/sheet/number-format.d.ts +63 -0
- package/dist/sheet/number-format.js +508 -0
- package/dist/sheet/palette.d.ts +19 -0
- package/dist/sheet/palette.js +84 -0
- package/dist/sheet/parse.d.ts +13 -0
- package/dist/sheet/parse.js +108 -0
- package/dist/sheet/paste-special.d.ts +164 -0
- package/dist/sheet/paste-special.js +592 -0
- package/dist/sheet/protection.d.ts +21 -0
- package/dist/sheet/protection.js +48 -0
- package/dist/sheet/rects.d.ts +45 -0
- package/dist/sheet/rects.js +112 -0
- package/dist/sheet/refs.d.ts +82 -0
- package/dist/sheet/refs.js +266 -0
- package/dist/sheet/ribbon-icons.d.ts +31 -0
- package/dist/sheet/ribbon-icons.js +350 -0
- package/dist/sheet/ribbon.d.ts +173 -0
- package/dist/sheet/ribbon.js +753 -0
- package/dist/sheet/shortcuts.d.ts +164 -0
- package/dist/sheet/shortcuts.js +623 -0
- package/dist/sheet/structure.d.ts +87 -0
- package/dist/sheet/structure.js +166 -0
- package/dist/sheet/tables.d.ts +70 -0
- package/dist/sheet/tables.js +147 -0
- package/dist/sheet/tokenize.d.ts +54 -0
- package/dist/sheet/tokenize.js +304 -0
- package/dist/sheet/transforms.d.ts +76 -0
- package/dist/sheet/transforms.js +154 -0
- package/dist/sheet/validation.d.ts +92 -0
- package/dist/sheet/validation.js +241 -0
- package/dist/sheet/workbook.d.ts +72 -0
- package/dist/sheet/workbook.js +409 -0
- package/dist/sheet-enable.d.ts +6 -0
- package/dist/sheet-enable.js +46 -0
- package/dist/studio/ui-components.generated.js +63 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +9 -4
- package/dist/designer/assets/GridMenus-B1JtIN21.js +0 -8
- package/dist/designer/assets/SvDateRangeInput-CjO7nn_x.js +0 -1
- package/dist/designer/assets/SvDateTimePicker-ByBD_ov6.js +0 -1
- package/dist/designer/assets/SvField-YDLipR6O.css +0 -1
- package/dist/designer/assets/SvField-cj1PFNgZ.js +0 -1
- package/dist/designer/assets/SvGridCellEditor-0W1L2hJ1.js +0 -2
- package/dist/designer/assets/SvGridChart-DBOR4xcz.js +0 -2
- package/dist/designer/assets/SvGridChart-fkMWeh1J.css +0 -1
- package/dist/designer/assets/SvGridChartPanel-symrhNnR.js +0 -11
- package/dist/designer/assets/SvGridChartView-BmRxq6mU.js +0 -1
- package/dist/designer/assets/SvListBox-1oUF6Iav.js +0 -110
- package/dist/designer/assets/chart-DXFganU8.js +0 -1
- package/dist/designer/assets/column-resize-C_inr5zT.js +0 -1
- package/dist/designer/assets/date-format-DGagAqRc.css +0 -1
- package/dist/designer/assets/date-format-onlYDsrY.js +0 -1
- package/dist/designer/assets/easing-zaypKrr0.js +0 -3
- package/dist/designer/assets/index-k2stW6L8.css +0 -1
- package/dist/designer/assets/row-resize-B0FWHcNr.js +0 -1
- package/dist/designer/assets/src-D9p27OGC.js +0 -58
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The formula AST and the error vocabulary, in their own module so the
|
|
3
|
+
* tokenizer, parser, evaluator and the reference rewriter can all import the
|
|
4
|
+
* shapes without importing each other.
|
|
5
|
+
*/
|
|
6
|
+
import type { CellRef } from './address.js';
|
|
7
|
+
export type SheetError = '#REF!' | '#CYCLE!' | '#DIV/0!' | '#VALUE!' | '#NAME?' | '#NUM!' | '#N/A' | '#PARSE!';
|
|
8
|
+
/** A computed cell value. Errors are values, not exceptions, once evaluation
|
|
9
|
+
* finishes - that is what lets `=IFERROR(A1, 0)` see one. */
|
|
10
|
+
export type CellValue = string | number | boolean | {
|
|
11
|
+
error: SheetError;
|
|
12
|
+
};
|
|
13
|
+
export declare function isError(v: CellValue): v is {
|
|
14
|
+
error: SheetError;
|
|
15
|
+
};
|
|
16
|
+
export declare function err(code: SheetError): {
|
|
17
|
+
error: SheetError;
|
|
18
|
+
};
|
|
19
|
+
/** Thrown during evaluation and caught at the boundary, where it becomes a
|
|
20
|
+
* value. Carrying the code on a real Error keeps stack traces useful in dev
|
|
21
|
+
* without the evaluator having to thread a result type through every branch. */
|
|
22
|
+
export declare class FormulaError extends Error {
|
|
23
|
+
readonly code: SheetError;
|
|
24
|
+
constructor(code: SheetError);
|
|
25
|
+
}
|
|
26
|
+
export type Node = {
|
|
27
|
+
k: 'num';
|
|
28
|
+
v: number;
|
|
29
|
+
} | {
|
|
30
|
+
k: 'str';
|
|
31
|
+
v: string;
|
|
32
|
+
} | {
|
|
33
|
+
k: 'bool';
|
|
34
|
+
v: boolean;
|
|
35
|
+
}
|
|
36
|
+
/** A single cell. `ref.row` is never null here; a column-only reference
|
|
37
|
+
* parses as a range instead. */
|
|
38
|
+
| {
|
|
39
|
+
k: 'ref';
|
|
40
|
+
ref: CellRef;
|
|
41
|
+
}
|
|
42
|
+
/** A rectangle. Either end's row may be null, meaning a whole-column ref
|
|
43
|
+
* that runs to the last used row of the sheet. */
|
|
44
|
+
| {
|
|
45
|
+
k: 'range';
|
|
46
|
+
from: CellRef;
|
|
47
|
+
to: CellRef;
|
|
48
|
+
}
|
|
49
|
+
/** A defined name (`=Tax`), resolved against the workbook at evaluation. */
|
|
50
|
+
| {
|
|
51
|
+
k: 'name';
|
|
52
|
+
name: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A structured reference: `Orders[Amount]`, `[@Amount]`, `Orders[#Totals]`.
|
|
56
|
+
*
|
|
57
|
+
* Deliberately NOT resolved to a range here. The range a table reference
|
|
58
|
+
* means depends on how many rows the table has right now, and the AST is
|
|
59
|
+
* cached across the edits that change that. Resolving at parse time is how
|
|
60
|
+
* a total silently stops covering rows added after it was typed, which is
|
|
61
|
+
* the exact problem tables exist to fix.
|
|
62
|
+
*/
|
|
63
|
+
| {
|
|
64
|
+
k: 'table';
|
|
65
|
+
/** null for the unqualified `[Amount]` form, which only means
|
|
66
|
+
* something inside a table. */
|
|
67
|
+
table: string | null;
|
|
68
|
+
/** null when the reference names no column, e.g. `Orders[#All]`. */
|
|
69
|
+
column: string | null;
|
|
70
|
+
/** The far end of a column span: `Orders[[Qty]:[Amount]]`. */
|
|
71
|
+
columnTo?: string | null;
|
|
72
|
+
specifier: '#All' | '#Data' | '#Headers' | '#Totals' | '#ThisRow';
|
|
73
|
+
} | {
|
|
74
|
+
k: 'unary';
|
|
75
|
+
op: '-' | '+' | '%';
|
|
76
|
+
arg: Node;
|
|
77
|
+
} | {
|
|
78
|
+
k: 'binary';
|
|
79
|
+
op: BinaryOp;
|
|
80
|
+
left: Node;
|
|
81
|
+
right: Node;
|
|
82
|
+
} | {
|
|
83
|
+
k: 'fn';
|
|
84
|
+
name: string;
|
|
85
|
+
args: Node[];
|
|
86
|
+
};
|
|
87
|
+
export type BinaryOp = '+' | '-' | '*' | '/' | '^' | '&' | '=' | '<>' | '<' | '>' | '<=' | '>=';
|
|
88
|
+
/** Binding power. Comparison loosest, then concat, then arithmetic. */
|
|
89
|
+
export declare const PRECEDENCE: Record<BinaryOp, number>;
|
|
90
|
+
/** Walk every node, parents before children. */
|
|
91
|
+
export declare function visit(node: Node, fn: (n: Node) => void): void;
|
|
92
|
+
/** Rebuild the tree with `fn` applied to every node, deepest first. Used by
|
|
93
|
+
* the reference rewriter, which must not mutate a cached AST in place. */
|
|
94
|
+
export declare function mapNode(node: Node, fn: (n: Node) => Node): Node;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export function isError(v) {
|
|
2
|
+
return typeof v === 'object' && v !== null && 'error' in v;
|
|
3
|
+
}
|
|
4
|
+
export function err(code) {
|
|
5
|
+
return { error: code };
|
|
6
|
+
}
|
|
7
|
+
/** Thrown during evaluation and caught at the boundary, where it becomes a
|
|
8
|
+
* value. Carrying the code on a real Error keeps stack traces useful in dev
|
|
9
|
+
* without the evaluator having to thread a result type through every branch. */
|
|
10
|
+
export class FormulaError extends Error {
|
|
11
|
+
constructor(code) {
|
|
12
|
+
super(code);
|
|
13
|
+
this.name = 'FormulaError';
|
|
14
|
+
this.code = code;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Binding power. Comparison loosest, then concat, then arithmetic. */
|
|
18
|
+
export const PRECEDENCE = {
|
|
19
|
+
'=': 1, '<>': 1, '<': 1, '>': 1, '<=': 1, '>=': 1,
|
|
20
|
+
'&': 2,
|
|
21
|
+
'+': 3, '-': 3,
|
|
22
|
+
'*': 4, '/': 4,
|
|
23
|
+
'^': 5,
|
|
24
|
+
};
|
|
25
|
+
/** Walk every node, parents before children. */
|
|
26
|
+
export function visit(node, fn) {
|
|
27
|
+
fn(node);
|
|
28
|
+
switch (node.k) {
|
|
29
|
+
case 'unary':
|
|
30
|
+
visit(node.arg, fn);
|
|
31
|
+
break;
|
|
32
|
+
case 'binary':
|
|
33
|
+
visit(node.left, fn);
|
|
34
|
+
visit(node.right, fn);
|
|
35
|
+
break;
|
|
36
|
+
case 'fn':
|
|
37
|
+
for (const a of node.args)
|
|
38
|
+
visit(a, fn);
|
|
39
|
+
break;
|
|
40
|
+
default: break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** Rebuild the tree with `fn` applied to every node, deepest first. Used by
|
|
44
|
+
* the reference rewriter, which must not mutate a cached AST in place. */
|
|
45
|
+
export function mapNode(node, fn) {
|
|
46
|
+
switch (node.k) {
|
|
47
|
+
case 'unary':
|
|
48
|
+
return fn({ ...node, arg: mapNode(node.arg, fn) });
|
|
49
|
+
case 'binary':
|
|
50
|
+
return fn({ ...node, left: mapNode(node.left, fn), right: mapNode(node.right, fn) });
|
|
51
|
+
case 'fn':
|
|
52
|
+
return fn({ ...node, args: node.args.map((a) => mapNode(a, fn)) });
|
|
53
|
+
default:
|
|
54
|
+
return fn(node);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Excel's AutoFilter: arrows on the header row of a region, each opening a
|
|
3
|
+
* menu of the column's values to tick and untick, or a condition (equals,
|
|
4
|
+
* greater than, contains, between...), and the rows that fail fold away.
|
|
5
|
+
* The rows are hidden through the grid's collapsed rows, kept apart from
|
|
6
|
+
* the rows the user hid by hand so Unhide and the saved document see
|
|
7
|
+
* only those.
|
|
8
|
+
*
|
|
9
|
+
* This module is the pure part: the state, the values a column offers,
|
|
10
|
+
* which rows a state hides, and how the state moves with an insert or
|
|
11
|
+
* delete. Conditions run through the grid's own Excel-filter compiler, so
|
|
12
|
+
* the operator semantics are the ones the grid's filter row has.
|
|
13
|
+
*/
|
|
14
|
+
import { type ExcelFilterOperator } from '@svgrid/grid/filtering';
|
|
15
|
+
import { type CellValue } from './ast.js';
|
|
16
|
+
import type { StructuralEdit } from './refs.js';
|
|
17
|
+
import { type Rect } from './rects.js';
|
|
18
|
+
export type FilterCondition = {
|
|
19
|
+
op: ExcelFilterOperator;
|
|
20
|
+
value?: string;
|
|
21
|
+
valueTo?: string;
|
|
22
|
+
};
|
|
23
|
+
export type ColumnFilter = {
|
|
24
|
+
kind: 'values'; /** Display texts left unticked; '' stands for the blanks. */
|
|
25
|
+
excluded: string[];
|
|
26
|
+
} | {
|
|
27
|
+
kind: 'condition';
|
|
28
|
+
first: FilterCondition;
|
|
29
|
+
join?: 'and' | 'or';
|
|
30
|
+
second?: FilterCondition;
|
|
31
|
+
};
|
|
32
|
+
export type AutoFilterState = {
|
|
33
|
+
/** The region, header row first. */
|
|
34
|
+
range: Rect;
|
|
35
|
+
/** Filters by column index; a column absent here shows every row. */
|
|
36
|
+
filters: Record<number, ColumnFilter>;
|
|
37
|
+
};
|
|
38
|
+
export type FilterValue = {
|
|
39
|
+
text: string;
|
|
40
|
+
count: number;
|
|
41
|
+
numeric: number | null;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The values a column offers, each once with its count: numbers first
|
|
45
|
+
* in order, then text A to Z, the blanks last. `rowVisible` leaves out the
|
|
46
|
+
* rows other columns' filters hide, as Excel's list does.
|
|
47
|
+
*/
|
|
48
|
+
export declare function distinctValues(state: AutoFilterState, col: number, valueAt: (r: number, c: number) => CellValue, displayAt: (r: number, c: number) => string, rowVisible?: (r: number) => boolean): FilterValue[];
|
|
49
|
+
/** Whether a row passes one column's filter. */
|
|
50
|
+
export declare function passesFilter(filter: ColumnFilter, value: CellValue, display: string): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* The rows the state hides: every row of the region under the header
|
|
53
|
+
* that fails a column's filter. Blanks past the region's edge are not in
|
|
54
|
+
* it, as Excel's region is what was filtered.
|
|
55
|
+
*/
|
|
56
|
+
export declare function hiddenRowsFor(state: AutoFilterState | null, valueAt: (r: number, c: number) => CellValue, displayAt: (r: number, c: number) => string): Set<number>;
|
|
57
|
+
/** Whether any column is filtered. */
|
|
58
|
+
export declare function isFiltering(state: AutoFilterState | null): boolean;
|
|
59
|
+
/** The state with one column's filter set, or cleared with null. */
|
|
60
|
+
export declare function withColumnFilter(state: AutoFilterState, col: number, filter: ColumnFilter | null): AutoFilterState;
|
|
61
|
+
/** A values filter from the ticked list, or null when everything is ticked. */
|
|
62
|
+
export declare function valuesFilter(all: ReadonlyArray<string>, ticked: ReadonlySet<string>): ColumnFilter | null;
|
|
63
|
+
/** The state after an insert or delete: the region moves, the filters follow their columns. */
|
|
64
|
+
export declare function shiftAutoFilter(state: AutoFilterState | null, edit: StructuralEdit): AutoFilterState | null;
|
|
65
|
+
/** A filter in words, for the menu's heading. */
|
|
66
|
+
export declare function describeFilter(filter: ColumnFilter): string;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Excel's AutoFilter: arrows on the header row of a region, each opening a
|
|
3
|
+
* menu of the column's values to tick and untick, or a condition (equals,
|
|
4
|
+
* greater than, contains, between...), and the rows that fail fold away.
|
|
5
|
+
* The rows are hidden through the grid's collapsed rows, kept apart from
|
|
6
|
+
* the rows the user hid by hand so Unhide and the saved document see
|
|
7
|
+
* only those.
|
|
8
|
+
*
|
|
9
|
+
* This module is the pure part: the state, the values a column offers,
|
|
10
|
+
* which rows a state hides, and how the state moves with an insert or
|
|
11
|
+
* delete. Conditions run through the grid's own Excel-filter compiler, so
|
|
12
|
+
* the operator semantics are the ones the grid's filter row has.
|
|
13
|
+
*/
|
|
14
|
+
import { compileExcelFilter } from '@svgrid/grid/filtering';
|
|
15
|
+
import { isError } from './ast.js';
|
|
16
|
+
import { shiftRect, lineShift } from './rects.js';
|
|
17
|
+
/** The operators that compare as numbers; the rest compare the display text. */
|
|
18
|
+
const NUMERIC_OPS = new Set(['greaterThan', 'lessThan', 'between']);
|
|
19
|
+
/**
|
|
20
|
+
* The values a column offers, each once with its count: numbers first
|
|
21
|
+
* in order, then text A to Z, the blanks last. `rowVisible` leaves out the
|
|
22
|
+
* rows other columns' filters hide, as Excel's list does.
|
|
23
|
+
*/
|
|
24
|
+
export function distinctValues(state, col, valueAt, displayAt, rowVisible = () => true) {
|
|
25
|
+
const seen = new Map();
|
|
26
|
+
for (let r = state.range[0] + 1; r <= state.range[2]; r += 1) {
|
|
27
|
+
if (!rowVisible(r))
|
|
28
|
+
continue;
|
|
29
|
+
const text = displayAt(r, col);
|
|
30
|
+
const v = valueAt(r, col);
|
|
31
|
+
const entry = seen.get(text);
|
|
32
|
+
if (entry) {
|
|
33
|
+
entry.count += 1;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
seen.set(text, { text, count: 1, numeric: typeof v === 'number' && Number.isFinite(v) ? v : null });
|
|
37
|
+
}
|
|
38
|
+
return [...seen.values()].sort((a, b) => {
|
|
39
|
+
if (a.text === '')
|
|
40
|
+
return 1;
|
|
41
|
+
if (b.text === '')
|
|
42
|
+
return -1;
|
|
43
|
+
if (a.numeric !== null && b.numeric !== null)
|
|
44
|
+
return a.numeric - b.numeric;
|
|
45
|
+
if (a.numeric !== null)
|
|
46
|
+
return -1;
|
|
47
|
+
if (b.numeric !== null)
|
|
48
|
+
return 1;
|
|
49
|
+
return a.text.localeCompare(b.text, undefined, { sensitivity: 'base' });
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function passesCondition(cond, value, display) {
|
|
53
|
+
const test = compileExcelFilter({ id: 'f', operator: cond.op, value: cond.value, valueTo: cond.valueTo });
|
|
54
|
+
if (NUMERIC_OPS.has(cond.op)) {
|
|
55
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
56
|
+
return false;
|
|
57
|
+
return test(value);
|
|
58
|
+
}
|
|
59
|
+
if (cond.op === 'isBlank' || cond.op === 'isNotBlank')
|
|
60
|
+
return test(display === '' ? null : display);
|
|
61
|
+
return test(isError(value) ? value.error : display);
|
|
62
|
+
}
|
|
63
|
+
/** Whether a row passes one column's filter. */
|
|
64
|
+
export function passesFilter(filter, value, display) {
|
|
65
|
+
if (filter.kind === 'values')
|
|
66
|
+
return !filter.excluded.includes(display);
|
|
67
|
+
const first = passesCondition(filter.first, value, display);
|
|
68
|
+
if (!filter.second)
|
|
69
|
+
return first;
|
|
70
|
+
const second = passesCondition(filter.second, value, display);
|
|
71
|
+
return filter.join === 'or' ? first || second : first && second;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The rows the state hides: every row of the region under the header
|
|
75
|
+
* that fails a column's filter. Blanks past the region's edge are not in
|
|
76
|
+
* it, as Excel's region is what was filtered.
|
|
77
|
+
*/
|
|
78
|
+
export function hiddenRowsFor(state, valueAt, displayAt) {
|
|
79
|
+
const out = new Set();
|
|
80
|
+
if (!state)
|
|
81
|
+
return out;
|
|
82
|
+
const columns = Object.entries(state.filters).map(([c, f]) => [Number(c), f]);
|
|
83
|
+
if (!columns.length)
|
|
84
|
+
return out;
|
|
85
|
+
for (let r = state.range[0] + 1; r <= state.range[2]; r += 1) {
|
|
86
|
+
for (const [c, filter] of columns) {
|
|
87
|
+
if (!passesFilter(filter, valueAt(r, c), displayAt(r, c))) {
|
|
88
|
+
out.add(r);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
/** Whether any column is filtered. */
|
|
96
|
+
export function isFiltering(state) {
|
|
97
|
+
return !!state && Object.keys(state.filters).length > 0;
|
|
98
|
+
}
|
|
99
|
+
/** The state with one column's filter set, or cleared with null. */
|
|
100
|
+
export function withColumnFilter(state, col, filter) {
|
|
101
|
+
const filters = { ...state.filters };
|
|
102
|
+
if (filter === null)
|
|
103
|
+
delete filters[col];
|
|
104
|
+
else
|
|
105
|
+
filters[col] = filter;
|
|
106
|
+
return { range: state.range, filters };
|
|
107
|
+
}
|
|
108
|
+
/** A values filter from the ticked list, or null when everything is ticked. */
|
|
109
|
+
export function valuesFilter(all, ticked) {
|
|
110
|
+
const excluded = all.filter((text) => !ticked.has(text));
|
|
111
|
+
return excluded.length ? { kind: 'values', excluded } : null;
|
|
112
|
+
}
|
|
113
|
+
/** The state after an insert or delete: the region moves, the filters follow their columns. */
|
|
114
|
+
export function shiftAutoFilter(state, edit) {
|
|
115
|
+
if (!state)
|
|
116
|
+
return null;
|
|
117
|
+
const range = shiftRect(state.range, edit);
|
|
118
|
+
if (!range)
|
|
119
|
+
return null;
|
|
120
|
+
if (edit.kind === 'insertRows' || edit.kind === 'deleteRows')
|
|
121
|
+
return { range, filters: { ...state.filters } };
|
|
122
|
+
const shift = lineShift(edit);
|
|
123
|
+
const filters = {};
|
|
124
|
+
for (const [c, f] of Object.entries(state.filters)) {
|
|
125
|
+
const next = shift(Number(c));
|
|
126
|
+
if (next !== null)
|
|
127
|
+
filters[next] = f;
|
|
128
|
+
}
|
|
129
|
+
return { range, filters };
|
|
130
|
+
}
|
|
131
|
+
const OP_WORDS = {
|
|
132
|
+
equals: 'equals', notEquals: 'does not equal', greaterThan: 'is greater than', lessThan: 'is less than',
|
|
133
|
+
between: 'is between', contains: 'contains', notContains: 'does not contain', startsWith: 'begins with',
|
|
134
|
+
endsWith: 'ends with', isBlank: 'is blank', isNotBlank: 'is not blank',
|
|
135
|
+
};
|
|
136
|
+
/** A filter in words, for the menu's heading. */
|
|
137
|
+
export function describeFilter(filter) {
|
|
138
|
+
if (filter.kind === 'values')
|
|
139
|
+
return filter.excluded.length === 1 ? '1 value hidden' : `${filter.excluded.length} values hidden`;
|
|
140
|
+
const one = (c) => {
|
|
141
|
+
const word = OP_WORDS[c.op] ?? c.op;
|
|
142
|
+
if (c.op === 'isBlank' || c.op === 'isNotBlank')
|
|
143
|
+
return word;
|
|
144
|
+
return c.op === 'between' ? `${word} ${c.value ?? ''} and ${c.valueTo ?? ''}` : `${word} ${c.value ?? ''}`;
|
|
145
|
+
};
|
|
146
|
+
return filter.second ? `${one(filter.first)} ${filter.join ?? 'and'} ${one(filter.second)}` : one(filter.first);
|
|
147
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type FunctionSuggestion = {
|
|
2
|
+
name: string;
|
|
3
|
+
/** Where the partial name starts, so accepting can splice it out. */
|
|
4
|
+
start: number;
|
|
5
|
+
/** Where the caret is, i.e. where the partial name ends. */
|
|
6
|
+
end: number;
|
|
7
|
+
};
|
|
8
|
+
/** Signature hints, for the ones whose argument order is not obvious. */
|
|
9
|
+
export declare const SIGNATURES: Record<string, string>;
|
|
10
|
+
/**
|
|
11
|
+
* The word being typed immediately before the caret, if it could be a function
|
|
12
|
+
* name. Returns null when the caret is not in a formula, or sits somewhere a
|
|
13
|
+
* function name cannot start (inside a string, or right after a reference).
|
|
14
|
+
*/
|
|
15
|
+
export declare function partialAt(text: string, caret: number): {
|
|
16
|
+
word: string;
|
|
17
|
+
start: number;
|
|
18
|
+
} | null;
|
|
19
|
+
/**
|
|
20
|
+
* Function names matching what is being typed.
|
|
21
|
+
*
|
|
22
|
+
* Prefix matches first, then anything merely containing the text, so typing
|
|
23
|
+
* "look" still finds VLOOKUP. Within each group, SHORTER names rank first:
|
|
24
|
+
* alphabetical order puts SUBSTITUTE above SUM for "SU", which is exactly
|
|
25
|
+
* backwards from what someone typing two letters wants.
|
|
26
|
+
*/
|
|
27
|
+
export declare function suggestFunctions(text: string, caret: number, limit?: number): FunctionSuggestion[];
|
|
28
|
+
/** Splice an accepted suggestion in, leaving the caret inside the parens. */
|
|
29
|
+
export declare function applySuggestion(text: string, suggestion: FunctionSuggestion): {
|
|
30
|
+
text: string;
|
|
31
|
+
caret: number;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Close the parentheses a formula left open, the way Excel corrects
|
|
35
|
+
* "=SUM(A1:A3" to "=SUM(A1:A3)" when it is entered. Quoted text is skipped,
|
|
36
|
+
* so a ")" inside a string neither closes nor needs closing. Text that is
|
|
37
|
+
* not a formula, or has more closers than openers, is returned as it is:
|
|
38
|
+
* that one is an error the engine should report, not paper over.
|
|
39
|
+
*/
|
|
40
|
+
export declare function balanceParens(text: string): string;
|
|
41
|
+
/** The signature hint for whatever call the caret is inside, if any. */
|
|
42
|
+
export declare function signatureAt(text: string, caret: number): string | null;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formula-bar autocomplete, kept out of the component so the interesting part
|
|
3
|
+
* is testable without mounting anything.
|
|
4
|
+
*
|
|
5
|
+
* The job is narrow: given the text in the bar and where the caret is, work out
|
|
6
|
+
* whether the user is partway through typing a function name, and if so which
|
|
7
|
+
* ones match. Everything else (arrow keys, accepting a suggestion) is a list
|
|
8
|
+
* with an index, which the component owns.
|
|
9
|
+
*/
|
|
10
|
+
import { FUNCTIONS } from './functions.js';
|
|
11
|
+
const NAMES = Object.keys(FUNCTIONS).sort();
|
|
12
|
+
/** Signature hints, for the ones whose argument order is not obvious. */
|
|
13
|
+
export const SIGNATURES = {
|
|
14
|
+
VLOOKUP: 'VLOOKUP(lookup, table, colIndex)',
|
|
15
|
+
HLOOKUP: 'HLOOKUP(lookup, table, rowIndex)',
|
|
16
|
+
XLOOKUP: 'XLOOKUP(lookup, haystack, results, [ifMissing])',
|
|
17
|
+
INDEX: 'INDEX(range, row, [col])',
|
|
18
|
+
MATCH: 'MATCH(lookup, range, [mode])',
|
|
19
|
+
SUMIF: 'SUMIF(range, criterion, [sumRange])',
|
|
20
|
+
SUMIFS: 'SUMIFS(sumRange, range, criterion, ...)',
|
|
21
|
+
COUNTIF: 'COUNTIF(range, criterion)',
|
|
22
|
+
COUNTIFS: 'COUNTIFS(range, criterion, ...)',
|
|
23
|
+
AVERAGEIF: 'AVERAGEIF(range, criterion, [avgRange])',
|
|
24
|
+
IF: 'IF(condition, then, else)',
|
|
25
|
+
IFS: 'IFS(condition, value, ...)',
|
|
26
|
+
IFERROR: 'IFERROR(value, fallback)',
|
|
27
|
+
SWITCH: 'SWITCH(subject, case, value, ..., [default])',
|
|
28
|
+
ROUND: 'ROUND(number, digits)',
|
|
29
|
+
TEXT: 'TEXT(number, pattern)',
|
|
30
|
+
TEXTJOIN: 'TEXTJOIN(separator, skipEmpty, ...)',
|
|
31
|
+
SUBSTITUTE: 'SUBSTITUTE(text, find, replace)',
|
|
32
|
+
MID: 'MID(text, start, length)',
|
|
33
|
+
DATE: 'DATE(year, month, day)',
|
|
34
|
+
EOMONTH: 'EOMONTH(date, months)',
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* The word being typed immediately before the caret, if it could be a function
|
|
38
|
+
* name. Returns null when the caret is not in a formula, or sits somewhere a
|
|
39
|
+
* function name cannot start (inside a string, or right after a reference).
|
|
40
|
+
*/
|
|
41
|
+
export function partialAt(text, caret) {
|
|
42
|
+
if (!text.startsWith('='))
|
|
43
|
+
return null;
|
|
44
|
+
const upto = text.slice(0, caret);
|
|
45
|
+
// Inside a string literal there is nothing to suggest. Count unescaped
|
|
46
|
+
// quotes: an odd number means the caret is inside one.
|
|
47
|
+
let quotes = 0;
|
|
48
|
+
for (let i = 0; i < upto.length; i += 1) {
|
|
49
|
+
if (upto[i] === '"') {
|
|
50
|
+
if (upto[i + 1] === '"') {
|
|
51
|
+
i += 1;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
quotes += 1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (quotes % 2 === 1)
|
|
58
|
+
return null;
|
|
59
|
+
const m = /([A-Za-z][A-Za-z0-9.]*)$/.exec(upto);
|
|
60
|
+
if (!m)
|
|
61
|
+
return null;
|
|
62
|
+
const word = m[1];
|
|
63
|
+
const start = caret - word.length;
|
|
64
|
+
// A word followed by digits is a cell reference being typed, not a function.
|
|
65
|
+
if (/\d$/.test(word))
|
|
66
|
+
return null;
|
|
67
|
+
return { word, start };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Function names matching what is being typed.
|
|
71
|
+
*
|
|
72
|
+
* Prefix matches first, then anything merely containing the text, so typing
|
|
73
|
+
* "look" still finds VLOOKUP. Within each group, SHORTER names rank first:
|
|
74
|
+
* alphabetical order puts SUBSTITUTE above SUM for "SU", which is exactly
|
|
75
|
+
* backwards from what someone typing two letters wants.
|
|
76
|
+
*/
|
|
77
|
+
export function suggestFunctions(text, caret, limit = 8) {
|
|
78
|
+
const partial = partialAt(text, caret);
|
|
79
|
+
if (!partial || partial.word.length < 1)
|
|
80
|
+
return [];
|
|
81
|
+
const upper = partial.word.toUpperCase();
|
|
82
|
+
const prefix = [];
|
|
83
|
+
const contains = [];
|
|
84
|
+
for (const name of NAMES) {
|
|
85
|
+
if (name === upper)
|
|
86
|
+
continue;
|
|
87
|
+
if (name.startsWith(upper))
|
|
88
|
+
prefix.push(name);
|
|
89
|
+
else if (name.includes(upper))
|
|
90
|
+
contains.push(name);
|
|
91
|
+
}
|
|
92
|
+
const byCloseness = (a, b) => a.length - b.length || a.localeCompare(b);
|
|
93
|
+
return [...prefix.sort(byCloseness), ...contains.sort(byCloseness)]
|
|
94
|
+
.slice(0, limit)
|
|
95
|
+
.map((name) => ({ name, start: partial.start, end: caret }));
|
|
96
|
+
}
|
|
97
|
+
/** Splice an accepted suggestion in, leaving the caret inside the parens. */
|
|
98
|
+
export function applySuggestion(text, suggestion) {
|
|
99
|
+
const before = text.slice(0, suggestion.start);
|
|
100
|
+
const after = text.slice(suggestion.end);
|
|
101
|
+
// Do not double up a parenthesis the user already typed.
|
|
102
|
+
const needsParen = !after.startsWith('(');
|
|
103
|
+
// Only the OPENING parenthesis, as Excel inserts it. Inserting the pair
|
|
104
|
+
// put the caret between them, and everyone who then typed the arguments
|
|
105
|
+
// finished with a ")" of their own and got =SUM(A1:A3)) and #PARSE!.
|
|
106
|
+
// The closing one is supplied at commit by `balanceParens` when it is
|
|
107
|
+
// still missing, which is what Excel does too.
|
|
108
|
+
const inserted = needsParen ? `${suggestion.name}(` : suggestion.name;
|
|
109
|
+
return {
|
|
110
|
+
text: before + inserted + after,
|
|
111
|
+
caret: before.length + inserted.length,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Close the parentheses a formula left open, the way Excel corrects
|
|
116
|
+
* "=SUM(A1:A3" to "=SUM(A1:A3)" when it is entered. Quoted text is skipped,
|
|
117
|
+
* so a ")" inside a string neither closes nor needs closing. Text that is
|
|
118
|
+
* not a formula, or has more closers than openers, is returned as it is:
|
|
119
|
+
* that one is an error the engine should report, not paper over.
|
|
120
|
+
*/
|
|
121
|
+
export function balanceParens(text) {
|
|
122
|
+
if (!text.startsWith('='))
|
|
123
|
+
return text;
|
|
124
|
+
let depth = 0;
|
|
125
|
+
let inString = false;
|
|
126
|
+
for (const ch of text) {
|
|
127
|
+
if (ch === '"')
|
|
128
|
+
inString = !inString;
|
|
129
|
+
else if (!inString && ch === '(')
|
|
130
|
+
depth += 1;
|
|
131
|
+
else if (!inString && ch === ')')
|
|
132
|
+
depth -= 1;
|
|
133
|
+
}
|
|
134
|
+
return depth > 0 ? text + ')'.repeat(depth) : text;
|
|
135
|
+
}
|
|
136
|
+
/** The signature hint for whatever call the caret is inside, if any. */
|
|
137
|
+
export function signatureAt(text, caret) {
|
|
138
|
+
if (!text.startsWith('='))
|
|
139
|
+
return null;
|
|
140
|
+
const upto = text.slice(0, caret);
|
|
141
|
+
// Walk back looking for an unclosed '(' and read the name before it.
|
|
142
|
+
let depth = 0;
|
|
143
|
+
for (let i = upto.length - 1; i >= 0; i -= 1) {
|
|
144
|
+
const ch = upto[i];
|
|
145
|
+
if (ch === ')') {
|
|
146
|
+
depth += 1;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (ch === '(') {
|
|
150
|
+
if (depth > 0) {
|
|
151
|
+
depth -= 1;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const name = /([A-Za-z][A-Za-z0-9.]*)$/.exec(upto.slice(0, i))?.[1];
|
|
155
|
+
if (!name)
|
|
156
|
+
return null;
|
|
157
|
+
const upper = name.toUpperCase();
|
|
158
|
+
return SIGNATURES[upper] ?? (upper in FUNCTIONS ? `${upper}(...)` : null);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Value coercions, shared by the evaluator and the function table.
|
|
3
|
+
*
|
|
4
|
+
* Excel's rules, not JavaScript's. The differences that matter:
|
|
5
|
+
* - An empty cell is 0 in arithmetic but is NOT counted by COUNT or averaged
|
|
6
|
+
* by AVERAGE, so "coerce to number" and "is a number" are separate tests.
|
|
7
|
+
* - Text that does not parse as a number is `#VALUE!`, not NaN. Returning NaN
|
|
8
|
+
* would let a bad cell poison a total silently.
|
|
9
|
+
* - Text comparison is case-insensitive: `="a"="A"` is TRUE.
|
|
10
|
+
* - Booleans are 1 and 0 in arithmetic but are skipped by numeric aggregates.
|
|
11
|
+
*/
|
|
12
|
+
import { type CellValue } from './ast.js';
|
|
13
|
+
export declare function toNumber(v: CellValue): number;
|
|
14
|
+
export declare function toBool(v: CellValue): boolean;
|
|
15
|
+
export declare function toText(v: CellValue): string;
|
|
16
|
+
/** Excel's `=` between two values. Text compares case-insensitively; a number
|
|
17
|
+
* never equals the text that looks like it. */
|
|
18
|
+
export declare function looseEquals(a: CellValue, b: CellValue): boolean;
|
|
19
|
+
/** Ordering for `<` and friends. Excel orders number < text < boolean. */
|
|
20
|
+
export declare function compare(a: CellValue, b: CellValue): number;
|
|
21
|
+
/** The numeric members of a flattened argument list. Blanks, text and booleans
|
|
22
|
+
* are skipped, which is what makes AVERAGE of a column with a header work. */
|
|
23
|
+
export declare function numericOnly(values: ReadonlyArray<CellValue>): number[];
|
|
24
|
+
/** Is this cell empty for COUNTA / COUNTBLANK purposes. */
|
|
25
|
+
export declare function isBlank(v: CellValue): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* An Excel criterion: a bare value matches by equality, a string starting with
|
|
28
|
+
* an operator compares (`">100"`, `"<>x"`). Used by SUMIF / COUNTIF / their
|
|
29
|
+
* plural forms, which all share this grammar.
|
|
30
|
+
*/
|
|
31
|
+
export declare function matchesCriterion(value: CellValue, criterion: CellValue): boolean;
|