@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,122 @@
|
|
|
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 { FormulaError, isError } from './ast.js';
|
|
13
|
+
export function toNumber(v) {
|
|
14
|
+
if (typeof v === 'number')
|
|
15
|
+
return v;
|
|
16
|
+
if (typeof v === 'boolean')
|
|
17
|
+
return v ? 1 : 0;
|
|
18
|
+
if (typeof v === 'string') {
|
|
19
|
+
if (v.trim() === '')
|
|
20
|
+
return 0;
|
|
21
|
+
const n = Number(v);
|
|
22
|
+
if (Number.isFinite(n))
|
|
23
|
+
return n;
|
|
24
|
+
throw new FormulaError('#VALUE!');
|
|
25
|
+
}
|
|
26
|
+
if (isError(v))
|
|
27
|
+
throw new FormulaError(v.error);
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
export function toBool(v) {
|
|
31
|
+
if (typeof v === 'boolean')
|
|
32
|
+
return v;
|
|
33
|
+
if (typeof v === 'number')
|
|
34
|
+
return v !== 0;
|
|
35
|
+
if (typeof v === 'string') {
|
|
36
|
+
const upper = v.trim().toUpperCase();
|
|
37
|
+
if (upper === 'TRUE')
|
|
38
|
+
return true;
|
|
39
|
+
if (upper === 'FALSE')
|
|
40
|
+
return false;
|
|
41
|
+
return v.length > 0;
|
|
42
|
+
}
|
|
43
|
+
if (isError(v))
|
|
44
|
+
throw new FormulaError(v.error);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
export function toText(v) {
|
|
48
|
+
if (typeof v === 'string')
|
|
49
|
+
return v;
|
|
50
|
+
if (typeof v === 'boolean')
|
|
51
|
+
return v ? 'TRUE' : 'FALSE';
|
|
52
|
+
if (typeof v === 'number')
|
|
53
|
+
return String(v);
|
|
54
|
+
if (isError(v))
|
|
55
|
+
throw new FormulaError(v.error);
|
|
56
|
+
return '';
|
|
57
|
+
}
|
|
58
|
+
/** Excel's `=` between two values. Text compares case-insensitively; a number
|
|
59
|
+
* never equals the text that looks like it. */
|
|
60
|
+
export function looseEquals(a, b) {
|
|
61
|
+
if (typeof a === 'number' && typeof b === 'number')
|
|
62
|
+
return a === b;
|
|
63
|
+
if (typeof a === 'string' && typeof b === 'string') {
|
|
64
|
+
return a.toLowerCase() === b.toLowerCase();
|
|
65
|
+
}
|
|
66
|
+
if (typeof a === 'boolean' && typeof b === 'boolean')
|
|
67
|
+
return a === b;
|
|
68
|
+
if (isError(a) || isError(b))
|
|
69
|
+
return false;
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
/** Ordering for `<` and friends. Excel orders number < text < boolean. */
|
|
73
|
+
export function compare(a, b) {
|
|
74
|
+
if (typeof a === 'number' && typeof b === 'number')
|
|
75
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
76
|
+
if (typeof a === 'string' && typeof b === 'string') {
|
|
77
|
+
const x = a.toLowerCase();
|
|
78
|
+
const y = b.toLowerCase();
|
|
79
|
+
return x < y ? -1 : x > y ? 1 : 0;
|
|
80
|
+
}
|
|
81
|
+
return toNumber(a) < toNumber(b) ? -1 : toNumber(a) > toNumber(b) ? 1 : 0;
|
|
82
|
+
}
|
|
83
|
+
/** The numeric members of a flattened argument list. Blanks, text and booleans
|
|
84
|
+
* are skipped, which is what makes AVERAGE of a column with a header work. */
|
|
85
|
+
export function numericOnly(values) {
|
|
86
|
+
const out = [];
|
|
87
|
+
for (const v of values) {
|
|
88
|
+
if (typeof v === 'number' && Number.isFinite(v))
|
|
89
|
+
out.push(v);
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
/** Is this cell empty for COUNTA / COUNTBLANK purposes. */
|
|
94
|
+
export function isBlank(v) {
|
|
95
|
+
return v === '' || v === null || v === undefined;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* An Excel criterion: a bare value matches by equality, a string starting with
|
|
99
|
+
* an operator compares (`">100"`, `"<>x"`). Used by SUMIF / COUNTIF / their
|
|
100
|
+
* plural forms, which all share this grammar.
|
|
101
|
+
*/
|
|
102
|
+
export function matchesCriterion(value, criterion) {
|
|
103
|
+
if (typeof criterion === 'string') {
|
|
104
|
+
const m = /^(<=|>=|<>|<|>|=)(.*)$/.exec(criterion.trim());
|
|
105
|
+
if (m) {
|
|
106
|
+
const opText = m[1];
|
|
107
|
+
const rest = m[2].trim();
|
|
108
|
+
const n = Number(rest);
|
|
109
|
+
const operand = rest !== '' && Number.isFinite(n) ? n : rest;
|
|
110
|
+
switch (opText) {
|
|
111
|
+
case '=': return looseEquals(value, operand);
|
|
112
|
+
case '<>': return !looseEquals(value, operand);
|
|
113
|
+
case '<': return compare(value, operand) < 0;
|
|
114
|
+
case '>': return compare(value, operand) > 0;
|
|
115
|
+
case '<=': return compare(value, operand) <= 0;
|
|
116
|
+
case '>=': return compare(value, operand) >= 0;
|
|
117
|
+
default: break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return looseEquals(value, criterion);
|
|
122
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The keyboard commands themselves, written against `GridCommandContext` so
|
|
3
|
+
* they stay testable without mounting a grid.
|
|
4
|
+
*
|
|
5
|
+
* Every one that writes more than one cell runs inside `cmd.batch()`, so the
|
|
6
|
+
* whole action is a single Ctrl+Z. That is the seam added in @svgrid/grid;
|
|
7
|
+
* without it a Ctrl+D down a hundred rows would take a hundred presses to undo
|
|
8
|
+
* and would evict the history behind it.
|
|
9
|
+
*
|
|
10
|
+
* Phase 1 fills VALUES. Excel also translates relative references as it fills,
|
|
11
|
+
* so `=A1*2` in the source row becomes `=A2*2` one row down; that needs the
|
|
12
|
+
* formula engine's AST and lands with it in phase 2. Until then a formula
|
|
13
|
+
* fills as literal text, which is wrong for formulas and right for everything
|
|
14
|
+
* else, and is why `fillDown` routes through one `translateForFill` hook
|
|
15
|
+
* rather than inlining the copy.
|
|
16
|
+
*/
|
|
17
|
+
import type { GridCommandContext } from '@svgrid/grid/shortcuts';
|
|
18
|
+
import { type Rect } from './navigate.js';
|
|
19
|
+
/**
|
|
20
|
+
* How a value copied from (srcRow, srcCol) to (dstRow, dstCol) is rewritten.
|
|
21
|
+
* Phase 1 is identity. Phase 2 swaps in the engine's `translateFormula`, which
|
|
22
|
+
* is why every fill path calls it rather than copying the value directly.
|
|
23
|
+
*/
|
|
24
|
+
export type FillTranslator = (value: unknown, delta: {
|
|
25
|
+
rows: number;
|
|
26
|
+
cols: number;
|
|
27
|
+
}) => unknown;
|
|
28
|
+
/** Install the formula-aware translator. The engine calls this in phase 2. */
|
|
29
|
+
export declare function setFillTranslator(fn: FillTranslator | null): void;
|
|
30
|
+
export declare function targetRect(cmd: GridCommandContext): Rect | null;
|
|
31
|
+
/**
|
|
32
|
+
* Ctrl+D. Copies the TOP row of the selection into every row below it.
|
|
33
|
+
*
|
|
34
|
+
* Excel has a second behaviour here: with only one cell selected it fills from
|
|
35
|
+
* the cell directly above instead, which is what makes Ctrl+D useful while
|
|
36
|
+
* typing down a column. Both are implemented.
|
|
37
|
+
*/
|
|
38
|
+
export declare function fillDown(cmd: GridCommandContext): boolean;
|
|
39
|
+
/** Ctrl+R. The same shape as fillDown, across instead of down. */
|
|
40
|
+
export declare function fillRight(cmd: GridCommandContext): boolean;
|
|
41
|
+
/** Write one value into every cell of the selection. Excel's Ctrl+Enter. */
|
|
42
|
+
export declare function fillSelection(cmd: GridCommandContext, value: unknown): boolean;
|
|
43
|
+
/** Ctrl+; and Ctrl+Shift+;. ISO rather than locale-formatted, because the
|
|
44
|
+
* value goes into the data and a column's own `format` decides how it reads;
|
|
45
|
+
* stamping a localised string would put display text in the model. */
|
|
46
|
+
export declare function stampNow(kind: 'date' | 'time' | 'datetime', now?: Date): string;
|
|
47
|
+
export declare function stampDate(cmd: GridCommandContext, kind: 'date' | 'time' | 'datetime', now?: Date): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Ctrl+'. Copies the cell above VERBATIM - no reference translation, which is
|
|
50
|
+
* the whole point of the shortcut: it is how you get a copy of the formula
|
|
51
|
+
* above to edit, rather than one that has already shifted.
|
|
52
|
+
*/
|
|
53
|
+
export declare function copyFromAbove(cmd: GridCommandContext): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Ctrl+Shift+". The VALUE of the cell above: what a formula there shows,
|
|
56
|
+
* not the formula, so a running total can be frozen as a number. With an
|
|
57
|
+
* engine attached the evaluated value is what goes in; without one there is
|
|
58
|
+
* nothing but the text, which is what Ctrl+' copies. An error above copies
|
|
59
|
+
* nothing.
|
|
60
|
+
*/
|
|
61
|
+
export declare function copyValueFromAbove(cmd: GridCommandContext): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Alt+=. The range Excel guesses for a SUM anchored at the active cell: the
|
|
64
|
+
* run of numbers directly above, or directly to the left when there is nothing
|
|
65
|
+
* above. Returns null when neither direction has numbers to add.
|
|
66
|
+
*/
|
|
67
|
+
export declare function guessSumRange(cmd: GridCommandContext, isNumeric?: (value: unknown) => boolean): Rect | null;
|
|
68
|
+
/**
|
|
69
|
+
* How to find out what a cell actually EVALUATES to.
|
|
70
|
+
*
|
|
71
|
+
* Cells hold raw text, so a cell containing `=SUM(A1:A3)` reads as the string
|
|
72
|
+
* "=SUM(A1:A3)" and `looksNumeric` says no. AutoSum under a column of
|
|
73
|
+
* subtotals then finds no run and declines, which is the opposite of what
|
|
74
|
+
* Excel does: there, a formula that produces a number IS a number.
|
|
75
|
+
*
|
|
76
|
+
* Optional. Without one, a sheet with no engine behind it behaves as before
|
|
77
|
+
* and only literal numbers count.
|
|
78
|
+
*/
|
|
79
|
+
export type SheetValueProbe = (row: number, col: number) => unknown;
|
|
80
|
+
export declare function setSheetValueProbe(fn: SheetValueProbe | null): void;
|
|
81
|
+
export declare function getSheetValueProbe(): SheetValueProbe | null;
|
|
82
|
+
/**
|
|
83
|
+
* The numeric test AutoSum should use for the cell at (row, col): the
|
|
84
|
+
* evaluated value when an engine is attached, the raw text otherwise.
|
|
85
|
+
*/
|
|
86
|
+
export declare function numericAt(cmd: GridCommandContext, row: number, col: number): boolean;
|
|
87
|
+
/** Default numeric test for AutoSum: a finite number, or a string that parses
|
|
88
|
+
* as one. A blank is not a number, so a gap ends the run. */
|
|
89
|
+
export declare function looksNumeric(value: unknown): boolean;
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { isBlankValue } from './navigate.js';
|
|
2
|
+
const identity = (value) => value;
|
|
3
|
+
let translator = identity;
|
|
4
|
+
/** Install the formula-aware translator. The engine calls this in phase 2. */
|
|
5
|
+
export function setFillTranslator(fn) {
|
|
6
|
+
translator = fn ?? identity;
|
|
7
|
+
}
|
|
8
|
+
/** The rectangle a command should act on: the active range, or the active cell
|
|
9
|
+
* alone when nothing is selected. Returns null when there is no active cell. */
|
|
10
|
+
/**
|
|
11
|
+
* Whether a command may write a cell. `setCellValue` trusts its caller, so
|
|
12
|
+
* a fill over a protected sheet has to ask for itself and leave the locked
|
|
13
|
+
* cells as they are, the way the grid's own fill handle does.
|
|
14
|
+
*/
|
|
15
|
+
function writable(cmd, r, c) {
|
|
16
|
+
return cmd.canEdit?.(r, c) !== false;
|
|
17
|
+
}
|
|
18
|
+
export function targetRect(cmd) {
|
|
19
|
+
const last = cmd.ranges[cmd.ranges.length - 1];
|
|
20
|
+
if (last)
|
|
21
|
+
return last;
|
|
22
|
+
const active = cmd.activeCell;
|
|
23
|
+
if (!active)
|
|
24
|
+
return null;
|
|
25
|
+
return [active.rowIndex, active.colIndex, active.rowIndex, active.colIndex];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Ctrl+D. Copies the TOP row of the selection into every row below it.
|
|
29
|
+
*
|
|
30
|
+
* Excel has a second behaviour here: with only one cell selected it fills from
|
|
31
|
+
* the cell directly above instead, which is what makes Ctrl+D useful while
|
|
32
|
+
* typing down a column. Both are implemented.
|
|
33
|
+
*/
|
|
34
|
+
export function fillDown(cmd) {
|
|
35
|
+
const rect = targetRect(cmd);
|
|
36
|
+
if (!rect)
|
|
37
|
+
return false;
|
|
38
|
+
const [minRow, minCol, maxRow, maxCol] = rect;
|
|
39
|
+
if (minRow === maxRow) {
|
|
40
|
+
// Single row selected: pull from the row above.
|
|
41
|
+
if (minRow === 0)
|
|
42
|
+
return false;
|
|
43
|
+
return cmd.batch(() => {
|
|
44
|
+
for (let c = minCol; c <= maxCol; c += 1) {
|
|
45
|
+
if (!writable(cmd, minRow, c))
|
|
46
|
+
continue;
|
|
47
|
+
const value = cmd.getCellValue(minRow - 1, c);
|
|
48
|
+
cmd.setCellValue(minRow, c, translator(value, { rows: 1, cols: 0 }));
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return cmd.batch(() => {
|
|
54
|
+
for (let c = minCol; c <= maxCol; c += 1) {
|
|
55
|
+
const source = cmd.getCellValue(minRow, c);
|
|
56
|
+
for (let r = minRow + 1; r <= maxRow; r += 1) {
|
|
57
|
+
if (!writable(cmd, r, c))
|
|
58
|
+
continue;
|
|
59
|
+
cmd.setCellValue(r, c, translator(source, { rows: r - minRow, cols: 0 }));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/** Ctrl+R. The same shape as fillDown, across instead of down. */
|
|
66
|
+
export function fillRight(cmd) {
|
|
67
|
+
const rect = targetRect(cmd);
|
|
68
|
+
if (!rect)
|
|
69
|
+
return false;
|
|
70
|
+
const [minRow, minCol, maxRow, maxCol] = rect;
|
|
71
|
+
if (minCol === maxCol) {
|
|
72
|
+
if (minCol === 0)
|
|
73
|
+
return false;
|
|
74
|
+
return cmd.batch(() => {
|
|
75
|
+
for (let r = minRow; r <= maxRow; r += 1) {
|
|
76
|
+
if (!writable(cmd, r, minCol))
|
|
77
|
+
continue;
|
|
78
|
+
const value = cmd.getCellValue(r, minCol - 1);
|
|
79
|
+
cmd.setCellValue(r, minCol, translator(value, { rows: 0, cols: 1 }));
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return cmd.batch(() => {
|
|
85
|
+
for (let r = minRow; r <= maxRow; r += 1) {
|
|
86
|
+
const source = cmd.getCellValue(r, minCol);
|
|
87
|
+
for (let c = minCol + 1; c <= maxCol; c += 1) {
|
|
88
|
+
if (!writable(cmd, r, c))
|
|
89
|
+
continue;
|
|
90
|
+
cmd.setCellValue(r, c, translator(source, { rows: 0, cols: c - minCol }));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/** Write one value into every cell of the selection. Excel's Ctrl+Enter. */
|
|
97
|
+
export function fillSelection(cmd, value) {
|
|
98
|
+
const rect = targetRect(cmd);
|
|
99
|
+
if (!rect)
|
|
100
|
+
return false;
|
|
101
|
+
const [minRow, minCol, maxRow, maxCol] = rect;
|
|
102
|
+
return cmd.batch(() => {
|
|
103
|
+
for (let r = minRow; r <= maxRow; r += 1) {
|
|
104
|
+
for (let c = minCol; c <= maxCol; c += 1) {
|
|
105
|
+
if (writable(cmd, r, c))
|
|
106
|
+
cmd.setCellValue(r, c, value);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/** Ctrl+; and Ctrl+Shift+;. ISO rather than locale-formatted, because the
|
|
113
|
+
* value goes into the data and a column's own `format` decides how it reads;
|
|
114
|
+
* stamping a localised string would put display text in the model. */
|
|
115
|
+
export function stampNow(kind, now = new Date()) {
|
|
116
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
117
|
+
const date = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
|
|
118
|
+
const time = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
|
|
119
|
+
if (kind === 'date')
|
|
120
|
+
return date;
|
|
121
|
+
if (kind === 'time')
|
|
122
|
+
return time;
|
|
123
|
+
return `${date} ${time}`;
|
|
124
|
+
}
|
|
125
|
+
export function stampDate(cmd, kind, now) {
|
|
126
|
+
return fillSelection(cmd, stampNow(kind, now));
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Ctrl+'. Copies the cell above VERBATIM - no reference translation, which is
|
|
130
|
+
* the whole point of the shortcut: it is how you get a copy of the formula
|
|
131
|
+
* above to edit, rather than one that has already shifted.
|
|
132
|
+
*/
|
|
133
|
+
export function copyFromAbove(cmd) {
|
|
134
|
+
const active = cmd.activeCell;
|
|
135
|
+
if (!active || active.rowIndex === 0)
|
|
136
|
+
return false;
|
|
137
|
+
if (!writable(cmd, active.rowIndex, active.colIndex))
|
|
138
|
+
return false;
|
|
139
|
+
const value = cmd.getCellValue(active.rowIndex - 1, active.colIndex);
|
|
140
|
+
cmd.setCellValue(active.rowIndex, active.colIndex, value);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Ctrl+Shift+". The VALUE of the cell above: what a formula there shows,
|
|
145
|
+
* not the formula, so a running total can be frozen as a number. With an
|
|
146
|
+
* engine attached the evaluated value is what goes in; without one there is
|
|
147
|
+
* nothing but the text, which is what Ctrl+' copies. An error above copies
|
|
148
|
+
* nothing.
|
|
149
|
+
*/
|
|
150
|
+
export function copyValueFromAbove(cmd) {
|
|
151
|
+
const active = cmd.activeCell;
|
|
152
|
+
if (!active || active.rowIndex === 0)
|
|
153
|
+
return false;
|
|
154
|
+
if (!writable(cmd, active.rowIndex, active.colIndex))
|
|
155
|
+
return false;
|
|
156
|
+
const probe = valueProbe;
|
|
157
|
+
const raw = cmd.getCellValue(active.rowIndex - 1, active.colIndex);
|
|
158
|
+
let value = raw;
|
|
159
|
+
if (probe) {
|
|
160
|
+
const shown = probe(active.rowIndex - 1, active.colIndex);
|
|
161
|
+
if (shown !== null && typeof shown === 'object')
|
|
162
|
+
return false;
|
|
163
|
+
if (shown !== undefined && shown !== null)
|
|
164
|
+
value = typeof shown === 'boolean' ? (shown ? 'TRUE' : 'FALSE') : String(shown);
|
|
165
|
+
}
|
|
166
|
+
cmd.setCellValue(active.rowIndex, active.colIndex, value);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Alt+=. The range Excel guesses for a SUM anchored at the active cell: the
|
|
171
|
+
* run of numbers directly above, or directly to the left when there is nothing
|
|
172
|
+
* above. Returns null when neither direction has numbers to add.
|
|
173
|
+
*/
|
|
174
|
+
export function guessSumRange(cmd, isNumeric = looksNumeric) {
|
|
175
|
+
const active = cmd.activeCell;
|
|
176
|
+
if (!active)
|
|
177
|
+
return null;
|
|
178
|
+
const { rowIndex: row, colIndex: col } = active;
|
|
179
|
+
// When an engine is attached the run is measured against evaluated values,
|
|
180
|
+
// so a column of SUM subtotals counts as numbers the way it does in Excel.
|
|
181
|
+
const numeric = valueProbe
|
|
182
|
+
? (r, c) => numericAt(cmd, r, c)
|
|
183
|
+
: (r, c) => isNumeric(cmd.getCellValue(r, c));
|
|
184
|
+
let top = row;
|
|
185
|
+
while (top > 0 && numeric(top - 1, col))
|
|
186
|
+
top -= 1;
|
|
187
|
+
if (top < row)
|
|
188
|
+
return [top, col, row - 1, col];
|
|
189
|
+
let left = col;
|
|
190
|
+
while (left > 0 && numeric(row, left - 1))
|
|
191
|
+
left -= 1;
|
|
192
|
+
if (left < col)
|
|
193
|
+
return [row, left, row, col - 1];
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
let valueProbe = null;
|
|
197
|
+
export function setSheetValueProbe(fn) {
|
|
198
|
+
valueProbe = fn;
|
|
199
|
+
}
|
|
200
|
+
export function getSheetValueProbe() {
|
|
201
|
+
return valueProbe;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* The numeric test AutoSum should use for the cell at (row, col): the
|
|
205
|
+
* evaluated value when an engine is attached, the raw text otherwise.
|
|
206
|
+
*/
|
|
207
|
+
export function numericAt(cmd, row, col) {
|
|
208
|
+
const probe = valueProbe;
|
|
209
|
+
if (!probe)
|
|
210
|
+
return looksNumeric(cmd.getCellValue(row, col));
|
|
211
|
+
const value = probe(row, col);
|
|
212
|
+
// An engine reports an error as an object; that is not a number, and it
|
|
213
|
+
// should end the run rather than be counted as one.
|
|
214
|
+
if (value !== null && typeof value === 'object')
|
|
215
|
+
return false;
|
|
216
|
+
return looksNumeric(value);
|
|
217
|
+
}
|
|
218
|
+
/** Default numeric test for AutoSum: a finite number, or a string that parses
|
|
219
|
+
* as one. A blank is not a number, so a gap ends the run. */
|
|
220
|
+
export function looksNumeric(value) {
|
|
221
|
+
if (isBlankValue(value))
|
|
222
|
+
return false;
|
|
223
|
+
if (typeof value === 'number')
|
|
224
|
+
return Number.isFinite(value);
|
|
225
|
+
if (typeof value === 'string') {
|
|
226
|
+
const n = Number(value.trim());
|
|
227
|
+
return value.trim() !== '' && Number.isFinite(n);
|
|
228
|
+
}
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { NotesMap } from './rects.js';
|
|
2
|
+
export type { NotesMap } from './rects.js';
|
|
3
|
+
/** The comment on (r, c), or undefined when there is none. */
|
|
4
|
+
export declare function commentAt(notes: NotesMap, r: number, c: number): string | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* The map with (r, c) set to `text`, or cleared when the text is blank.
|
|
7
|
+
* A new map, never the old one changed: the grid re-reads `notes` only
|
|
8
|
+
* when the object it is handed is a different one.
|
|
9
|
+
*/
|
|
10
|
+
export declare function withComment(notes: NotesMap, r: number, c: number, text: string): NotesMap;
|
|
11
|
+
/** Every comment, top to bottom then left to right. */
|
|
12
|
+
export declare function listComments(notes: NotesMap): Array<{
|
|
13
|
+
row: number;
|
|
14
|
+
col: number;
|
|
15
|
+
text: string;
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* The comment after (or before) `from`, reading the sheet row by row and
|
|
19
|
+
* wrapping at the end, as Excel's Next Comment and Previous Comment do. The
|
|
20
|
+
* cell itself is skipped, so pressing Next on a commented cell moves on;
|
|
21
|
+
* null when the sheet has no comments at all.
|
|
22
|
+
*/
|
|
23
|
+
export declare function nextComment(notes: NotesMap, from: {
|
|
24
|
+
row: number;
|
|
25
|
+
col: number;
|
|
26
|
+
}, dir: 1 | -1): {
|
|
27
|
+
row: number;
|
|
28
|
+
col: number;
|
|
29
|
+
} | null;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cell comments, Excel's notes: a text on a cell, shown as a corner mark
|
|
3
|
+
* and read on hover. They live in the sheet document in the grid's own
|
|
4
|
+
* `notes` shape (`r4` -> `B` -> text), so the grid draws the mark and the
|
|
5
|
+
* tooltip and the document moves them with an insert or delete
|
|
6
|
+
* (`remapNotes`). This module is the pure part: reading, writing, and
|
|
7
|
+
* walking them the way Review > Previous / Next do.
|
|
8
|
+
*/
|
|
9
|
+
import { colToLetters, lettersToCol } from './address.js';
|
|
10
|
+
/** The comment on (r, c), or undefined when there is none. */
|
|
11
|
+
export function commentAt(notes, r, c) {
|
|
12
|
+
const text = notes[`r${r}`]?.[colToLetters(c)];
|
|
13
|
+
return text && text.trim() ? text : undefined;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The map with (r, c) set to `text`, or cleared when the text is blank.
|
|
17
|
+
* A new map, never the old one changed: the grid re-reads `notes` only
|
|
18
|
+
* when the object it is handed is a different one.
|
|
19
|
+
*/
|
|
20
|
+
export function withComment(notes, r, c, text) {
|
|
21
|
+
const rowId = `r${r}`;
|
|
22
|
+
const columnId = colToLetters(c);
|
|
23
|
+
const out = {};
|
|
24
|
+
for (const [id, line] of Object.entries(notes))
|
|
25
|
+
out[id] = { ...line };
|
|
26
|
+
if (text.trim()) {
|
|
27
|
+
out[rowId] = { ...(out[rowId] ?? {}), [columnId]: text };
|
|
28
|
+
}
|
|
29
|
+
else if (out[rowId]) {
|
|
30
|
+
delete out[rowId][columnId];
|
|
31
|
+
if (Object.keys(out[rowId]).length === 0)
|
|
32
|
+
delete out[rowId];
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
/** Every comment, top to bottom then left to right. */
|
|
37
|
+
export function listComments(notes) {
|
|
38
|
+
const out = [];
|
|
39
|
+
for (const [rowId, line] of Object.entries(notes)) {
|
|
40
|
+
const row = Number(rowId.slice(1));
|
|
41
|
+
if (!Number.isInteger(row) || row < 0)
|
|
42
|
+
continue;
|
|
43
|
+
for (const [columnId, text] of Object.entries(line)) {
|
|
44
|
+
const col = lettersToCol(columnId);
|
|
45
|
+
if (col < 0 || !text || !text.trim())
|
|
46
|
+
continue;
|
|
47
|
+
out.push({ row, col, text });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return out.sort((a, b) => a.row - b.row || a.col - b.col);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The comment after (or before) `from`, reading the sheet row by row and
|
|
54
|
+
* wrapping at the end, as Excel's Next Comment and Previous Comment do. The
|
|
55
|
+
* cell itself is skipped, so pressing Next on a commented cell moves on;
|
|
56
|
+
* null when the sheet has no comments at all.
|
|
57
|
+
*/
|
|
58
|
+
export function nextComment(notes, from, dir) {
|
|
59
|
+
const all = listComments(notes);
|
|
60
|
+
if (!all.length)
|
|
61
|
+
return null;
|
|
62
|
+
const after = (a, b) => a.row > b.row || (a.row === b.row && a.col > b.col);
|
|
63
|
+
const at = (entry) => ({ row: entry.row, col: entry.col });
|
|
64
|
+
if (dir === 1) {
|
|
65
|
+
const hit = all.find((entry) => after(entry, from));
|
|
66
|
+
return at(hit ?? all[0]);
|
|
67
|
+
}
|
|
68
|
+
for (let i = all.length - 1; i >= 0; i -= 1) {
|
|
69
|
+
if (after(from, all[i]))
|
|
70
|
+
return at(all[i]);
|
|
71
|
+
}
|
|
72
|
+
return at(all[all.length - 1]);
|
|
73
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conditional formatting, Excel's: rules over cells that colour them by
|
|
3
|
+
* what they hold. Highlight Cells Rules (greater than, between, text that
|
|
4
|
+
* contains, duplicates), Top/Bottom Rules (top 10, above average), Data
|
|
5
|
+
* Bars, Color Scales and Icon Sets, each over rectangles that move with
|
|
6
|
+
* an insert or delete.
|
|
7
|
+
*
|
|
8
|
+
* Rules are evaluated over COMPUTED values (what a formula shows, not its
|
|
9
|
+
* text), which is why the model lives here beside the workbook rather than
|
|
10
|
+
* in the grid's own value-driven rules. The statistics a rule needs over
|
|
11
|
+
* its range (min, max, mean, rank, counts) are computed once per repaint by
|
|
12
|
+
* the shell and handed in, so a thousand cells cost one pass.
|
|
13
|
+
*
|
|
14
|
+
* Priority is array order, first rule first, as Excel's Manage Rules lists
|
|
15
|
+
* them: the first rule that decides a property wins it, and a rule with
|
|
16
|
+
* Stop If True stops the rules below it for the cells it matches.
|
|
17
|
+
*/
|
|
18
|
+
import { type CellValue } from './ast.js';
|
|
19
|
+
import type { StructuralEdit } from './refs.js';
|
|
20
|
+
import { type Rect } from './rects.js';
|
|
21
|
+
import type { CellFormatEntry } from './format-store.js';
|
|
22
|
+
export type CfStyle = Pick<CellFormatEntry, 'fill' | 'color' | 'bold' | 'italic' | 'underline' | 'strike' | 'numFmt'>;
|
|
23
|
+
export type CfOperator = 'greater' | 'less' | 'between' | 'notBetween' | 'equal' | 'notEqual' | 'greaterOrEqual' | 'lessOrEqual';
|
|
24
|
+
export type CfTextMatch = 'contains' | 'notContains' | 'beginsWith' | 'endsWith';
|
|
25
|
+
export type CfIconSet = 'arrows' | 'traffic' | 'flags' | 'symbols';
|
|
26
|
+
export type CfScaleColors = readonly [string, string] | readonly [string, string, string];
|
|
27
|
+
export type CfRule = {
|
|
28
|
+
id: string;
|
|
29
|
+
rects: ReadonlyArray<Rect>;
|
|
30
|
+
stopIfTrue?: boolean;
|
|
31
|
+
} & ({
|
|
32
|
+
kind: 'cellIs';
|
|
33
|
+
operator: CfOperator;
|
|
34
|
+
value1: string;
|
|
35
|
+
value2?: string;
|
|
36
|
+
style: CfStyle;
|
|
37
|
+
} | {
|
|
38
|
+
kind: 'text';
|
|
39
|
+
match: CfTextMatch;
|
|
40
|
+
value: string;
|
|
41
|
+
style: CfStyle;
|
|
42
|
+
} | {
|
|
43
|
+
kind: 'duplicates';
|
|
44
|
+
unique?: boolean;
|
|
45
|
+
style: CfStyle;
|
|
46
|
+
} | {
|
|
47
|
+
kind: 'topBottom';
|
|
48
|
+
top: boolean;
|
|
49
|
+
rank: number;
|
|
50
|
+
percent?: boolean;
|
|
51
|
+
style: CfStyle;
|
|
52
|
+
} | {
|
|
53
|
+
kind: 'average';
|
|
54
|
+
above: boolean;
|
|
55
|
+
style: CfStyle;
|
|
56
|
+
} | {
|
|
57
|
+
kind: 'dataBar';
|
|
58
|
+
color: string;
|
|
59
|
+
} | {
|
|
60
|
+
kind: 'colorScale';
|
|
61
|
+
colors: CfScaleColors;
|
|
62
|
+
} | {
|
|
63
|
+
kind: 'iconSet';
|
|
64
|
+
set: CfIconSet;
|
|
65
|
+
});
|
|
66
|
+
export type CfKind = CfRule['kind'];
|
|
67
|
+
/** A rule that carries a style: what the small dialogs make. */
|
|
68
|
+
export type CfStyledRule = Extract<CfRule, {
|
|
69
|
+
style: CfStyle;
|
|
70
|
+
}>;
|
|
71
|
+
/** Omit that keeps a union a union, one member at a time. */
|
|
72
|
+
type DistributiveOmit<T, K extends keyof never> = T extends unknown ? Omit<T, K> : never;
|
|
73
|
+
/** Any rule without its id and rectangles: what a new rule is made from. */
|
|
74
|
+
export type CfRuleBody = DistributiveOmit<CfRule, 'id' | 'rects'>;
|
|
75
|
+
/** A styled rule without its id and rectangles: what the dialog hands back. */
|
|
76
|
+
export type CfBody = DistributiveOmit<CfStyledRule, 'id' | 'rects'>;
|
|
77
|
+
/** The small dialogs, one per ribbon entry. */
|
|
78
|
+
export type CfPreset = 'greater' | 'less' | 'between' | 'equal' | 'text' | 'duplicates' | 'top10' | 'bottom10' | 'aboveAverage' | 'belowAverage';
|
|
79
|
+
/** What one rule knows about the numbers in its rectangles. */
|
|
80
|
+
export type CfStats = {
|
|
81
|
+
/** Numbers, sorted ascending. */
|
|
82
|
+
sorted: number[];
|
|
83
|
+
min: number;
|
|
84
|
+
max: number;
|
|
85
|
+
mean: number;
|
|
86
|
+
/** How often each display text occurs, for Duplicate Values. */
|
|
87
|
+
counts: Map<string, number>;
|
|
88
|
+
};
|
|
89
|
+
/** What the rules say about one cell. */
|
|
90
|
+
export type CfResult = {
|
|
91
|
+
style?: CfStyle;
|
|
92
|
+
/** A bar across the cell, 0..1 of its width. */
|
|
93
|
+
dataBar?: {
|
|
94
|
+
ratio: number;
|
|
95
|
+
color: string;
|
|
96
|
+
};
|
|
97
|
+
/** The icon set and which of its three icons: 0 = the top one. */
|
|
98
|
+
icon?: {
|
|
99
|
+
set: CfIconSet;
|
|
100
|
+
index: 0 | 1 | 2;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
/** How a rule reads the sheet: the shell wires the workbook in. */
|
|
104
|
+
export type CfContext = {
|
|
105
|
+
/** A formula or a literal, evaluated in the sheet. */
|
|
106
|
+
evaluate(text: string): CellValue;
|
|
107
|
+
};
|
|
108
|
+
/** The six "with" styles of Excel's Highlight Cells dialogs. */
|
|
109
|
+
export declare const CF_PRESET_STYLES: ReadonlyArray<{
|
|
110
|
+
id: string;
|
|
111
|
+
label: string;
|
|
112
|
+
style: CfStyle;
|
|
113
|
+
}>;
|
|
114
|
+
export declare const DATA_BAR_COLOR = "#638EC6";
|
|
115
|
+
export declare const COLOR_SCALES: Record<'green-yellow-red' | 'red-yellow-green' | 'green-white' | 'white-red', CfScaleColors>;
|
|
116
|
+
export declare function cfId(): string;
|
|
117
|
+
/**
|
|
118
|
+
* The numbers and the texts a rule's rectangles hold. One pass; the shell
|
|
119
|
+
* caches the result per rule until the next repaint.
|
|
120
|
+
*/
|
|
121
|
+
export declare function ruleStats(rule: CfRule, valueAt: (r: number, c: number) => CellValue, displayAt: (r: number, c: number) => string): CfStats;
|
|
122
|
+
/** The colour a scale gives a position 0..1. */
|
|
123
|
+
export declare function scaleColor(colors: CfScaleColors, t: number): string;
|
|
124
|
+
/** Which of an icon set's three icons a position 0..1 gets: Excel's thirds. */
|
|
125
|
+
export declare function iconIndex(t: number): 0 | 1 | 2;
|
|
126
|
+
/**
|
|
127
|
+
* What the rules say about (r, c). Array order is priority; the first rule
|
|
128
|
+
* that decides a property (the fill, the text colour, the bar, the icon)
|
|
129
|
+
* keeps it; a matching rule with Stop If True ends the walk.
|
|
130
|
+
*/
|
|
131
|
+
export declare function evaluateCf(rules: ReadonlyArray<CfRule>, r: number, c: number, value: CellValue, display: string, statsFor: (rule: CfRule) => CfStats, ctx: CfContext): CfResult | null;
|
|
132
|
+
/** The rules after an insert or delete. */
|
|
133
|
+
export declare function shiftCf(rules: ReadonlyArray<CfRule>, edit: StructuralEdit): CfRule[];
|
|
134
|
+
/** The rules with the rectangles cut out of them: Clear Rules from Selected Cells. */
|
|
135
|
+
export declare function removeCf(rules: ReadonlyArray<CfRule>, rects: ReadonlyArray<Rect>): CfRule[];
|
|
136
|
+
/** The rules touching any of the rectangles. */
|
|
137
|
+
export declare function cfIn(rules: ReadonlyArray<CfRule>, rects: ReadonlyArray<Rect>): CfRule[];
|
|
138
|
+
/** A rule in words, the way Excel's Manage Rules lists it. */
|
|
139
|
+
export declare function describeCf(rule: CfRule): string;
|
|
140
|
+
/** Whether a rule carries a style the dialog can edit. */
|
|
141
|
+
export declare function hasStyle(rule: CfRule): rule is Extract<CfRule, {
|
|
142
|
+
style: CfStyle;
|
|
143
|
+
}>;
|
|
144
|
+
export {};
|