@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,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The evaluator.
|
|
3
|
+
*
|
|
4
|
+
* Errors travel as VALUES once evaluation finishes, but as thrown
|
|
5
|
+
* `FormulaError`s while it runs. That split is deliberate: a coercion buried
|
|
6
|
+
* three calls deep inside a function needs to abort the whole expression, and
|
|
7
|
+
* threading a result type through every branch of every builtin would swamp the
|
|
8
|
+
* table. The throw is caught here, at the one boundary, and becomes a value.
|
|
9
|
+
*
|
|
10
|
+
* Four functions never reach the table because they must NOT have every
|
|
11
|
+
* argument evaluated first: IF and IFS pick a branch, IFERROR needs to catch,
|
|
12
|
+
* and SWITCH matches before evaluating. Evaluating both branches of an IF is
|
|
13
|
+
* not just wasted work - `=IF(A1=0, 0, 100/A1)` would raise #DIV/0! from the
|
|
14
|
+
* branch it did not take.
|
|
15
|
+
*/
|
|
16
|
+
import { FormulaError, isError, err, } from './ast.js';
|
|
17
|
+
import { toNumber, toBool, toText, looseEquals, compare } from './coerce.js';
|
|
18
|
+
import { withCustomFunctions } from './functions.js';
|
|
19
|
+
import { resolveTableRange, columnIndexOf, } from './tables.js';
|
|
20
|
+
function rangeGrid(from, to, ctx) {
|
|
21
|
+
const sheet = from.sheet ?? to.sheet;
|
|
22
|
+
const r1 = Math.min(from.row ?? 0, to.row ?? from.row ?? 0);
|
|
23
|
+
const r2 = to.row === null || from.row === null
|
|
24
|
+
? ctx.lastRow(sheet)
|
|
25
|
+
: Math.max(from.row, to.row);
|
|
26
|
+
const c1 = Math.min(from.col, to.col);
|
|
27
|
+
const c2 = Math.max(from.col, to.col);
|
|
28
|
+
const out = [];
|
|
29
|
+
for (let r = r1; r <= r2; r += 1) {
|
|
30
|
+
const row = [];
|
|
31
|
+
for (let c = c1; c <= c2; c += 1)
|
|
32
|
+
row.push(ctx.resolve(sheet, r, c));
|
|
33
|
+
out.push(row);
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
function binary(op, l, r) {
|
|
38
|
+
switch (op) {
|
|
39
|
+
case '+': return toNumber(l) + toNumber(r);
|
|
40
|
+
case '-': return toNumber(l) - toNumber(r);
|
|
41
|
+
case '*': return toNumber(l) * toNumber(r);
|
|
42
|
+
case '/': {
|
|
43
|
+
const d = toNumber(r);
|
|
44
|
+
return d === 0 ? err('#DIV/0!') : toNumber(l) / d;
|
|
45
|
+
}
|
|
46
|
+
case '^': return Math.pow(toNumber(l), toNumber(r));
|
|
47
|
+
case '&': return toText(l) + toText(r);
|
|
48
|
+
case '=': return looseEquals(l, r);
|
|
49
|
+
case '<>': return !looseEquals(l, r);
|
|
50
|
+
case '<': return compare(l, r) < 0;
|
|
51
|
+
case '>': return compare(l, r) > 0;
|
|
52
|
+
case '<=': return compare(l, r) <= 0;
|
|
53
|
+
case '>=': return compare(l, r) >= 0;
|
|
54
|
+
default: return err('#VALUE!');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Functions handled before the table, because their arguments must not all
|
|
58
|
+
* be evaluated up front. */
|
|
59
|
+
const SHORT_CIRCUIT = new Set(['IF', 'IFS', 'IFERROR', 'IFNA', 'SWITCH']);
|
|
60
|
+
function evalNode(node, ctx) {
|
|
61
|
+
switch (node.k) {
|
|
62
|
+
case 'num': return node.v;
|
|
63
|
+
case 'str': return node.v;
|
|
64
|
+
case 'bool': return node.v;
|
|
65
|
+
case 'ref':
|
|
66
|
+
return ctx.resolve(node.ref.sheet, node.ref.row ?? 0, node.ref.col);
|
|
67
|
+
case 'range': {
|
|
68
|
+
// A range in scalar position collapses to its top-left cell, which is
|
|
69
|
+
// what Excel does outside an array context.
|
|
70
|
+
const grid = rangeGrid(node.from, node.to, ctx);
|
|
71
|
+
return grid[0]?.[0] ?? '';
|
|
72
|
+
}
|
|
73
|
+
case 'name': {
|
|
74
|
+
const target = nameTarget(node.name, ctx);
|
|
75
|
+
if (target)
|
|
76
|
+
return evalNode(target, ctx);
|
|
77
|
+
const v = ctx.resolveName?.(node.name);
|
|
78
|
+
return v === undefined ? err('#NAME?') : v;
|
|
79
|
+
}
|
|
80
|
+
case 'unary': {
|
|
81
|
+
const v = evalNode(node.arg, ctx);
|
|
82
|
+
if (isError(v))
|
|
83
|
+
return v;
|
|
84
|
+
if (node.op === '-')
|
|
85
|
+
return -toNumber(v);
|
|
86
|
+
if (node.op === '%')
|
|
87
|
+
return toNumber(v) / 100;
|
|
88
|
+
return toNumber(v);
|
|
89
|
+
}
|
|
90
|
+
case 'binary': {
|
|
91
|
+
const l = evalNode(node.left, ctx);
|
|
92
|
+
if (isError(l))
|
|
93
|
+
return l;
|
|
94
|
+
const r = evalNode(node.right, ctx);
|
|
95
|
+
if (isError(r))
|
|
96
|
+
return r;
|
|
97
|
+
return binary(node.op, l, r);
|
|
98
|
+
}
|
|
99
|
+
case 'table':
|
|
100
|
+
return evalTableRef(node, ctx);
|
|
101
|
+
case 'fn':
|
|
102
|
+
return evalCall(node, ctx);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Turn a structured reference into a rectangle and read it.
|
|
107
|
+
*
|
|
108
|
+
* Every failure here is #REF! rather than a throw, because a table reference
|
|
109
|
+
* naming a column that was renamed is an ordinary thing to find in a sheet,
|
|
110
|
+
* and one broken total should not take the rest of the workbook with it.
|
|
111
|
+
*/
|
|
112
|
+
function tableRectOf(node, ctx) {
|
|
113
|
+
const here = ctx.currentCell;
|
|
114
|
+
const table = node.table
|
|
115
|
+
? ctx.findTable?.(node.table)
|
|
116
|
+
: here
|
|
117
|
+
? ctx.tableAt?.(here.sheet, here.row, here.col)
|
|
118
|
+
: undefined;
|
|
119
|
+
if (!table)
|
|
120
|
+
return null;
|
|
121
|
+
const headerAt = (sheet, row, col) => {
|
|
122
|
+
const v = ctx.resolve(sheet, row, col);
|
|
123
|
+
return isError(v) ? '' : toText(v);
|
|
124
|
+
};
|
|
125
|
+
let columns = null;
|
|
126
|
+
if (node.column !== null) {
|
|
127
|
+
const from = columnIndexOf(table, node.column, headerAt);
|
|
128
|
+
if (from < 0)
|
|
129
|
+
return null;
|
|
130
|
+
const to = node.columnTo != null
|
|
131
|
+
? columnIndexOf(table, node.columnTo, headerAt)
|
|
132
|
+
: from;
|
|
133
|
+
if (to < 0)
|
|
134
|
+
return null;
|
|
135
|
+
columns = { from: Math.min(from, to), to: Math.max(from, to) };
|
|
136
|
+
}
|
|
137
|
+
return resolveTableRange(table, node.specifier, columns, here ? here.row : null);
|
|
138
|
+
}
|
|
139
|
+
function evalTableRef(node, ctx) {
|
|
140
|
+
const rect = tableRectOf(node, ctx);
|
|
141
|
+
if (!rect)
|
|
142
|
+
return err('#REF!');
|
|
143
|
+
// Scalar position collapses to the top-left, the same as an A1 range.
|
|
144
|
+
return ctx.resolve(rect.sheet, rect.firstRow, rect.firstCol);
|
|
145
|
+
}
|
|
146
|
+
/** Every cell of a structured reference, for the aggregate functions. */
|
|
147
|
+
function tableGrid(node, ctx) {
|
|
148
|
+
const rect = tableRectOf(node, ctx);
|
|
149
|
+
if (!rect)
|
|
150
|
+
return null;
|
|
151
|
+
const out = [];
|
|
152
|
+
for (let r = rect.firstRow; r <= rect.lastRow; r += 1) {
|
|
153
|
+
const line = [];
|
|
154
|
+
for (let c = rect.firstCol; c <= rect.lastCol; c += 1)
|
|
155
|
+
line.push(ctx.resolve(rect.sheet, r, c));
|
|
156
|
+
out.push(line);
|
|
157
|
+
}
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The reference a defined name stands for, or null.
|
|
162
|
+
*
|
|
163
|
+
* A name may be defined as another name (`Sales` -> `Q3Sales`), so this
|
|
164
|
+
* follows the chain. It stops after a handful of hops rather than walking a
|
|
165
|
+
* circular definition forever: `A` -> `B` -> `A` is a mistake in the Name
|
|
166
|
+
* Manager, and the formula that used it reads #NAME? rather than hanging.
|
|
167
|
+
*/
|
|
168
|
+
function nameTarget(name, ctx) {
|
|
169
|
+
let node = ctx.resolveNameNode?.(name) ?? null;
|
|
170
|
+
for (let hops = 0; node && node.k === 'name'; hops += 1) {
|
|
171
|
+
if (hops >= 8)
|
|
172
|
+
return null;
|
|
173
|
+
node = ctx.resolveNameNode?.(node.name) ?? null;
|
|
174
|
+
}
|
|
175
|
+
return node;
|
|
176
|
+
}
|
|
177
|
+
/** Functions whose arguments must not all be evaluated up front. */
|
|
178
|
+
function evalCall(node, ctx) {
|
|
179
|
+
const { name, args } = node;
|
|
180
|
+
// Every short-circuiting function below indexes its arguments directly.
|
|
181
|
+
// `=IF()` parses fine, so without this the index is undefined and the
|
|
182
|
+
// TypeError escapes the boundary this module promises never to throw past.
|
|
183
|
+
if (SHORT_CIRCUIT.has(name) && args.length === 0)
|
|
184
|
+
return err('#VALUE!');
|
|
185
|
+
if (name === 'IF') {
|
|
186
|
+
const cond = evalNode(args[0], ctx);
|
|
187
|
+
if (isError(cond))
|
|
188
|
+
return cond;
|
|
189
|
+
const branch = toBool(cond) ? args[1] : args[2];
|
|
190
|
+
if (!branch)
|
|
191
|
+
return toBool(cond);
|
|
192
|
+
return evalNode(branch, ctx);
|
|
193
|
+
}
|
|
194
|
+
if (name === 'IFERROR' || name === 'IFNA') {
|
|
195
|
+
try {
|
|
196
|
+
const v = evalNode(args[0], ctx);
|
|
197
|
+
if (!isError(v))
|
|
198
|
+
return v;
|
|
199
|
+
if (name === 'IFNA' && v.error !== '#N/A')
|
|
200
|
+
return v;
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
if (!(e instanceof FormulaError))
|
|
204
|
+
throw e;
|
|
205
|
+
if (name === 'IFNA' && e.code !== '#N/A')
|
|
206
|
+
return err(e.code);
|
|
207
|
+
}
|
|
208
|
+
return args[1] ? evalNode(args[1], ctx) : err('#N/A');
|
|
209
|
+
}
|
|
210
|
+
if (name === 'IFS') {
|
|
211
|
+
for (let i = 0; i + 1 < args.length; i += 2) {
|
|
212
|
+
const cond = evalNode(args[i], ctx);
|
|
213
|
+
if (isError(cond))
|
|
214
|
+
return cond;
|
|
215
|
+
if (toBool(cond))
|
|
216
|
+
return evalNode(args[i + 1], ctx);
|
|
217
|
+
}
|
|
218
|
+
return err('#N/A');
|
|
219
|
+
}
|
|
220
|
+
if (name === 'SWITCH') {
|
|
221
|
+
const subject = evalNode(args[0], ctx);
|
|
222
|
+
if (isError(subject))
|
|
223
|
+
return subject;
|
|
224
|
+
let i = 1;
|
|
225
|
+
for (; i + 1 < args.length; i += 2) {
|
|
226
|
+
const candidate = evalNode(args[i], ctx);
|
|
227
|
+
if (isError(candidate))
|
|
228
|
+
return candidate;
|
|
229
|
+
if (looseEquals(subject, candidate))
|
|
230
|
+
return evalNode(args[i + 1], ctx);
|
|
231
|
+
}
|
|
232
|
+
// A trailing odd argument is the default.
|
|
233
|
+
return i < args.length ? evalNode(args[i], ctx) : err('#N/A');
|
|
234
|
+
}
|
|
235
|
+
const table = ctx.functions ?? withCustomFunctions(undefined);
|
|
236
|
+
const fn = table[name];
|
|
237
|
+
if (!fn)
|
|
238
|
+
return err('#NAME?');
|
|
239
|
+
// Evaluate every argument, keeping range shape for the lookups that need it.
|
|
240
|
+
const perArg = [];
|
|
241
|
+
const grids = [];
|
|
242
|
+
for (const given of args) {
|
|
243
|
+
// A name stands for whatever it was defined as. Substituting the node
|
|
244
|
+
// here, before the shape check, is what lets `Sales` in =SUM(Sales) be a
|
|
245
|
+
// whole column rather than the top-left cell a scalar read would give.
|
|
246
|
+
const arg = given.k === 'name' ? (nameTarget(given.name, ctx) ?? given) : given;
|
|
247
|
+
if (arg.k === 'range') {
|
|
248
|
+
const grid = rangeGrid(arg.from, arg.to, ctx);
|
|
249
|
+
grids.push(grid);
|
|
250
|
+
perArg.push(grid.flat());
|
|
251
|
+
}
|
|
252
|
+
else if (arg.k === 'table') {
|
|
253
|
+
// A structured reference expands exactly as a range does, which is
|
|
254
|
+
// what makes =SUM(Orders[Amount]) work.
|
|
255
|
+
const grid = tableGrid(arg, ctx);
|
|
256
|
+
if (!grid) {
|
|
257
|
+
grids.push(null);
|
|
258
|
+
perArg.push([err('#REF!')]);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
grids.push(grid);
|
|
262
|
+
perArg.push(grid.flat());
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
grids.push(null);
|
|
267
|
+
perArg.push([evalNode(arg, ctx)]);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const flat = perArg.flat();
|
|
271
|
+
// An error anywhere in the arguments propagates, except for the counting
|
|
272
|
+
// functions, which Excel lets see errors in their range.
|
|
273
|
+
if (name !== 'COUNTA' && name !== 'COUNTBLANK') {
|
|
274
|
+
for (const v of flat)
|
|
275
|
+
if (isError(v))
|
|
276
|
+
return v;
|
|
277
|
+
}
|
|
278
|
+
const payload = { flat, args: perArg, grids };
|
|
279
|
+
return fn(payload);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Evaluate a parsed formula. Never throws: a `FormulaError` raised anywhere
|
|
283
|
+
* inside becomes the matching error value.
|
|
284
|
+
*/
|
|
285
|
+
/**
|
|
286
|
+
* The values a node stands for as a grid: a range, a reference (one cell),
|
|
287
|
+
* or a name that refers to either. Null for anything else, which is how a
|
|
288
|
+
* caller tells "a list source that is a range" from "a formula".
|
|
289
|
+
*/
|
|
290
|
+
export function rangeValues(node, ctx) {
|
|
291
|
+
try {
|
|
292
|
+
if (node.k === 'range')
|
|
293
|
+
return rangeGrid(node.from, node.to, ctx);
|
|
294
|
+
if (node.k === 'ref')
|
|
295
|
+
return [[ctx.resolve(node.ref.sheet, node.ref.row ?? 0, node.ref.col)]];
|
|
296
|
+
if (node.k === 'name') {
|
|
297
|
+
const target = nameTarget(node.name, ctx);
|
|
298
|
+
return target ? rangeValues(target, ctx) : null;
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
catch (e) {
|
|
303
|
+
if (e instanceof FormulaError)
|
|
304
|
+
return [[err(e.code)]];
|
|
305
|
+
throw e;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
export function evaluate(node, ctx) {
|
|
309
|
+
try {
|
|
310
|
+
return evalNode(node, ctx);
|
|
311
|
+
}
|
|
312
|
+
catch (e) {
|
|
313
|
+
if (e instanceof FormulaError)
|
|
314
|
+
return err(e.code);
|
|
315
|
+
if (e instanceof RangeError)
|
|
316
|
+
return err('#NUM!');
|
|
317
|
+
// Last resort. This module promises callers it never throws, and a cell
|
|
318
|
+
// showing #VALUE! is recoverable where an exception out of a render pass
|
|
319
|
+
// is not.
|
|
320
|
+
if (e instanceof TypeError)
|
|
321
|
+
return err('#VALUE!');
|
|
322
|
+
throw e;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/** Render a computed value the way a cell shows it. */
|
|
326
|
+
export function formatValue(v) {
|
|
327
|
+
if (isError(v))
|
|
328
|
+
return v.error;
|
|
329
|
+
if (typeof v === 'boolean')
|
|
330
|
+
return v ? 'TRUE' : 'FALSE';
|
|
331
|
+
if (typeof v === 'number') {
|
|
332
|
+
if (!Number.isFinite(v))
|
|
333
|
+
return '#NUM!';
|
|
334
|
+
return String(v);
|
|
335
|
+
}
|
|
336
|
+
return String(v);
|
|
337
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find and Replace.
|
|
3
|
+
*
|
|
4
|
+
* The grid already has find: a Ctrl+F overlay over displayed values, with
|
|
5
|
+
* next/prev. What it has no notion of is replacing, and the two differ in more
|
|
6
|
+
* than direction.
|
|
7
|
+
*
|
|
8
|
+
* Finding looks at what the user SEES. Replacing has to write what the user
|
|
9
|
+
* TYPED, which for a formula cell is the formula, not its result. Replacing
|
|
10
|
+
* "100" inside a cell showing `100` that actually holds `=B2*C2` would either
|
|
11
|
+
* do nothing or destroy the formula, depending on which text you reached for.
|
|
12
|
+
* Hence `lookIn`, and hence replace always writing through the raw text.
|
|
13
|
+
*/
|
|
14
|
+
import type { GridCommandContext } from '@svgrid/grid/shortcuts';
|
|
15
|
+
export type FindOptions = {
|
|
16
|
+
matchCase?: boolean;
|
|
17
|
+
/** The cell must equal the search text, not merely contain it. */
|
|
18
|
+
matchEntireCell?: boolean;
|
|
19
|
+
/** Search the formula source, or the value a cell displays. Default 'values'. */
|
|
20
|
+
lookIn?: 'values' | 'formulas';
|
|
21
|
+
/** Restrict to the current selection. Default the whole sheet. */
|
|
22
|
+
scope?: 'sheet' | 'selection';
|
|
23
|
+
};
|
|
24
|
+
export type FindHit = {
|
|
25
|
+
rowIndex: number;
|
|
26
|
+
colIndex: number;
|
|
27
|
+
};
|
|
28
|
+
/** How a cell's text is obtained for each `lookIn` mode. */
|
|
29
|
+
export type FindTarget = {
|
|
30
|
+
/** The raw text the user typed, formula included. */
|
|
31
|
+
getRaw(rowIndex: number, colIndex: number): string;
|
|
32
|
+
/** What the cell displays. Defaults to the raw text when not supplied. */
|
|
33
|
+
getDisplay?(rowIndex: number, colIndex: number): string;
|
|
34
|
+
setRaw(rowIndex: number, colIndex: number, text: string): void;
|
|
35
|
+
/** Whether a cell may be written. Read-only cells are skipped by replace
|
|
36
|
+
* but still reported by find. */
|
|
37
|
+
isEditable?(rowIndex: number, colIndex: number): boolean;
|
|
38
|
+
onChange?(): void;
|
|
39
|
+
};
|
|
40
|
+
export declare function setFindTarget(next: FindTarget | null): void;
|
|
41
|
+
export declare function getFindTarget(): FindTarget | null;
|
|
42
|
+
/** Does `haystack` match `needle` under these options? */
|
|
43
|
+
export declare function cellMatches(haystack: string, needle: string, opts?: FindOptions): boolean;
|
|
44
|
+
/** Every cell matching, in reading order. */
|
|
45
|
+
export declare function findAll(cmd: GridCommandContext, needle: string, opts?: FindOptions): FindHit[];
|
|
46
|
+
/** The next hit after the active cell, wrapping. Null when there are none. */
|
|
47
|
+
export declare function findNext(cmd: GridCommandContext, needle: string, opts?: FindOptions, direction?: 1 | -1): FindHit | null;
|
|
48
|
+
/** Replace inside one string, honouring case sensitivity and whole-cell. */
|
|
49
|
+
export declare function replaceInText(haystack: string, needle: string, replacement: string, opts?: FindOptions): string;
|
|
50
|
+
/**
|
|
51
|
+
* Replace the active cell if it matches, then move to the next hit. Returns
|
|
52
|
+
* whether anything was written.
|
|
53
|
+
*/
|
|
54
|
+
export declare function replaceOne(cmd: GridCommandContext, needle: string, replacement: string, opts?: FindOptions): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Replace every match. Returns how many cells changed.
|
|
57
|
+
*
|
|
58
|
+
* Runs inside `cmd.batch`, so a hundred replacements are ONE Ctrl+Z. Without
|
|
59
|
+
* that this would be the single worst thing in the feature set for undo: it is
|
|
60
|
+
* the one command that routinely writes more cells than the history can hold.
|
|
61
|
+
*/
|
|
62
|
+
export declare function replaceAll(cmd: GridCommandContext, needle: string, replacement: string, opts?: FindOptions): number;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
let target = null;
|
|
2
|
+
export function setFindTarget(next) {
|
|
3
|
+
target = next;
|
|
4
|
+
}
|
|
5
|
+
export function getFindTarget() {
|
|
6
|
+
return target;
|
|
7
|
+
}
|
|
8
|
+
function textOf(t, r, c, lookIn) {
|
|
9
|
+
if (lookIn === 'formulas')
|
|
10
|
+
return t.getRaw(r, c);
|
|
11
|
+
return t.getDisplay ? t.getDisplay(r, c) : t.getRaw(r, c);
|
|
12
|
+
}
|
|
13
|
+
/** Does `haystack` match `needle` under these options? */
|
|
14
|
+
export function cellMatches(haystack, needle, opts = {}) {
|
|
15
|
+
if (needle === '')
|
|
16
|
+
return false;
|
|
17
|
+
const a = opts.matchCase ? haystack : haystack.toLowerCase();
|
|
18
|
+
const b = opts.matchCase ? needle : needle.toLowerCase();
|
|
19
|
+
return opts.matchEntireCell ? a === b : a.includes(b);
|
|
20
|
+
}
|
|
21
|
+
/** Every cell matching, in reading order. */
|
|
22
|
+
export function findAll(cmd, needle, opts = {}) {
|
|
23
|
+
const t = target;
|
|
24
|
+
if (!t || needle === '')
|
|
25
|
+
return [];
|
|
26
|
+
const lookIn = opts.lookIn ?? 'values';
|
|
27
|
+
const bounds = opts.scope === 'selection' ? cmd.ranges : null;
|
|
28
|
+
const hits = [];
|
|
29
|
+
const inScope = (r, c) => {
|
|
30
|
+
if (!bounds)
|
|
31
|
+
return true;
|
|
32
|
+
return bounds.some(([minRow, minCol, maxRow, maxCol]) => r >= minRow && r <= maxRow && c >= minCol && c <= maxCol);
|
|
33
|
+
};
|
|
34
|
+
for (let r = 0; r < cmd.rowCount; r += 1) {
|
|
35
|
+
for (let c = 0; c < cmd.colCount; c += 1) {
|
|
36
|
+
if (!inScope(r, c))
|
|
37
|
+
continue;
|
|
38
|
+
if (cellMatches(textOf(t, r, c, lookIn), needle, opts))
|
|
39
|
+
hits.push({ rowIndex: r, colIndex: c });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return hits;
|
|
43
|
+
}
|
|
44
|
+
/** The next hit after the active cell, wrapping. Null when there are none. */
|
|
45
|
+
export function findNext(cmd, needle, opts = {}, direction = 1) {
|
|
46
|
+
const hits = findAll(cmd, needle, opts);
|
|
47
|
+
if (hits.length === 0)
|
|
48
|
+
return null;
|
|
49
|
+
const active = cmd.activeCell;
|
|
50
|
+
if (!active)
|
|
51
|
+
return hits[0];
|
|
52
|
+
const key = (h) => h.rowIndex * cmd.colCount + h.colIndex;
|
|
53
|
+
const here = active.rowIndex * cmd.colCount + active.colIndex;
|
|
54
|
+
if (direction === 1) {
|
|
55
|
+
return hits.find((h) => key(h) > here) ?? hits[0];
|
|
56
|
+
}
|
|
57
|
+
// Reverse: the last hit strictly before the active cell, else wrap to the end.
|
|
58
|
+
for (let i = hits.length - 1; i >= 0; i -= 1) {
|
|
59
|
+
if (key(hits[i]) < here)
|
|
60
|
+
return hits[i];
|
|
61
|
+
}
|
|
62
|
+
return hits[hits.length - 1];
|
|
63
|
+
}
|
|
64
|
+
/** Replace inside one string, honouring case sensitivity and whole-cell. */
|
|
65
|
+
export function replaceInText(haystack, needle, replacement, opts = {}) {
|
|
66
|
+
if (needle === '')
|
|
67
|
+
return haystack;
|
|
68
|
+
if (opts.matchEntireCell) {
|
|
69
|
+
return cellMatches(haystack, needle, opts) ? replacement : haystack;
|
|
70
|
+
}
|
|
71
|
+
if (opts.matchCase)
|
|
72
|
+
return haystack.split(needle).join(replacement);
|
|
73
|
+
// Case-insensitive substring replace, without a regex: the needle is user
|
|
74
|
+
// input and escaping it correctly is more code than scanning for it.
|
|
75
|
+
const lowerHay = haystack.toLowerCase();
|
|
76
|
+
const lowerNeedle = needle.toLowerCase();
|
|
77
|
+
let out = '';
|
|
78
|
+
let i = 0;
|
|
79
|
+
for (;;) {
|
|
80
|
+
const at = lowerHay.indexOf(lowerNeedle, i);
|
|
81
|
+
if (at < 0) {
|
|
82
|
+
out += haystack.slice(i);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
out += haystack.slice(i, at) + replacement;
|
|
86
|
+
i = at + needle.length;
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Replace the active cell if it matches, then move to the next hit. Returns
|
|
92
|
+
* whether anything was written.
|
|
93
|
+
*/
|
|
94
|
+
export function replaceOne(cmd, needle, replacement, opts = {}) {
|
|
95
|
+
const t = target;
|
|
96
|
+
const active = cmd.activeCell;
|
|
97
|
+
if (!t || !active || needle === '')
|
|
98
|
+
return false;
|
|
99
|
+
const { rowIndex: r, colIndex: c } = active;
|
|
100
|
+
const lookIn = opts.lookIn ?? 'values';
|
|
101
|
+
const editable = t.isEditable?.(r, c) ?? true;
|
|
102
|
+
let wrote = false;
|
|
103
|
+
if (editable && cellMatches(textOf(t, r, c, lookIn), needle, opts)) {
|
|
104
|
+
// Always write through the RAW text: replacing inside a displayed value
|
|
105
|
+
// would overwrite a formula with its own result.
|
|
106
|
+
const raw = t.getRaw(r, c);
|
|
107
|
+
const next = replaceInText(raw, needle, replacement, opts);
|
|
108
|
+
if (next !== raw) {
|
|
109
|
+
t.setRaw(r, c, next);
|
|
110
|
+
t.onChange?.();
|
|
111
|
+
wrote = true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const following = findNext(cmd, needle, opts);
|
|
115
|
+
if (following) {
|
|
116
|
+
cmd.setActiveCell(following.rowIndex, following.colIndex);
|
|
117
|
+
cmd.setSelection(following.rowIndex, following.colIndex);
|
|
118
|
+
cmd.scrollIntoView(following.rowIndex, following.colIndex);
|
|
119
|
+
}
|
|
120
|
+
return wrote;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Replace every match. Returns how many cells changed.
|
|
124
|
+
*
|
|
125
|
+
* Runs inside `cmd.batch`, so a hundred replacements are ONE Ctrl+Z. Without
|
|
126
|
+
* that this would be the single worst thing in the feature set for undo: it is
|
|
127
|
+
* the one command that routinely writes more cells than the history can hold.
|
|
128
|
+
*/
|
|
129
|
+
export function replaceAll(cmd, needle, replacement, opts = {}) {
|
|
130
|
+
const t = target;
|
|
131
|
+
if (!t || needle === '')
|
|
132
|
+
return 0;
|
|
133
|
+
const hits = findAll(cmd, needle, opts);
|
|
134
|
+
if (hits.length === 0)
|
|
135
|
+
return 0;
|
|
136
|
+
return cmd.batch(() => {
|
|
137
|
+
let changed = 0;
|
|
138
|
+
for (const { rowIndex: r, colIndex: c } of hits) {
|
|
139
|
+
if (!(t.isEditable?.(r, c) ?? true))
|
|
140
|
+
continue;
|
|
141
|
+
const raw = t.getRaw(r, c);
|
|
142
|
+
const next = replaceInText(raw, needle, replacement, opts);
|
|
143
|
+
if (next === raw)
|
|
144
|
+
continue;
|
|
145
|
+
t.setRaw(r, c, next);
|
|
146
|
+
changed += 1;
|
|
147
|
+
}
|
|
148
|
+
if (changed > 0)
|
|
149
|
+
t.onChange?.();
|
|
150
|
+
return changed;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-cell formats and styles, keyed so they survive the grid moving rows
|
|
3
|
+
* around.
|
|
4
|
+
*
|
|
5
|
+
* Demo 27 keys its format map by DISPLAY row index. Sort the grid and every
|
|
6
|
+
* bold cell moves to whatever row now sits at that index, which is the kind of
|
|
7
|
+
* bug that looks like a rendering glitch and is actually lost data. This keys
|
|
8
|
+
* by `rowId:columnId`, the same shape the grid already uses for `notes`, so
|
|
9
|
+
* sorting, filtering, column reordering and pagination all leave it alone.
|
|
10
|
+
*
|
|
11
|
+
* Storage is sparse: a sheet of ten thousand cells with three bold ones holds
|
|
12
|
+
* three entries. Most cells have no entry at all and cost nothing.
|
|
13
|
+
*/
|
|
14
|
+
import type { BorderSpec } from '@svgrid/grid';
|
|
15
|
+
/** One cell's formatting. Every field optional; absent means inherit. */
|
|
16
|
+
/** The boolean fields `toggle` can flip. Every one of them is a plain
|
|
17
|
+
* on/off attribute of a cell, which is what makes Excel's all-on-turns-off
|
|
18
|
+
* rule the right one for all of them. */
|
|
19
|
+
export type ToggleableField = 'bold' | 'italic' | 'underline' | 'strike' | 'wrap';
|
|
20
|
+
export type CellFormatEntry = {
|
|
21
|
+
/** Excel number-format string, e.g. '#,##0.00'. */
|
|
22
|
+
numFmt?: string;
|
|
23
|
+
bold?: boolean;
|
|
24
|
+
italic?: boolean;
|
|
25
|
+
underline?: boolean;
|
|
26
|
+
strike?: boolean;
|
|
27
|
+
/** Text colour, any CSS colour. */
|
|
28
|
+
color?: string;
|
|
29
|
+
/** Background fill. */
|
|
30
|
+
fill?: string;
|
|
31
|
+
align?: 'left' | 'center' | 'right';
|
|
32
|
+
fontFamily?: string;
|
|
33
|
+
fontSize?: number;
|
|
34
|
+
wrap?: boolean;
|
|
35
|
+
indent?: number;
|
|
36
|
+
border?: {
|
|
37
|
+
top?: BorderSpec;
|
|
38
|
+
right?: BorderSpec;
|
|
39
|
+
bottom?: BorderSpec;
|
|
40
|
+
left?: BorderSpec;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Excel's Locked flag from Format Cells > Protection. Every cell is locked
|
|
44
|
+
* unless told otherwise, so only `false` is ever stored; the flag means
|
|
45
|
+
* nothing until the sheet is protected, when a locked cell refuses edits.
|
|
46
|
+
*/
|
|
47
|
+
locked?: boolean;
|
|
48
|
+
};
|
|
49
|
+
export type Rect = readonly [minRow: number, minCol: number, maxRow: number, maxCol: number];
|
|
50
|
+
/** Translates a display position to the stable ids the store keys on. */
|
|
51
|
+
export type CellAddressLookup = {
|
|
52
|
+
rowIdAt(rowIndex: number): string | null;
|
|
53
|
+
columnIdAt(colIndex: number): string | null;
|
|
54
|
+
};
|
|
55
|
+
export type SheetFormatStore = {
|
|
56
|
+
get(rowId: string, columnId: string): CellFormatEntry | undefined;
|
|
57
|
+
/** Merge `patch` into every cell of every rect. A field set to `undefined`
|
|
58
|
+
* is REMOVED, which is how you clear one attribute without clearing the
|
|
59
|
+
* cell; `clear` removes the whole entry. An entry left with no fields is
|
|
60
|
+
* dropped rather than kept as an empty object. */
|
|
61
|
+
set(rects: ReadonlyArray<Rect>, patch: CellFormatEntry, at: CellAddressLookup): void;
|
|
62
|
+
/** Remove all formatting from every cell of every rect. */
|
|
63
|
+
clear(rects: ReadonlyArray<Rect>, at: CellAddressLookup): void;
|
|
64
|
+
/** Toggle one boolean field across a range. Excel's rule: if EVERY cell in
|
|
65
|
+
* the range already has it, turn it off; otherwise turn it on. */
|
|
66
|
+
toggle(rects: ReadonlyArray<Rect>, field: ToggleableField, at: CellAddressLookup): void;
|
|
67
|
+
/** Drop every entry for a row. Call when a row is deleted, or the store
|
|
68
|
+
* leaks an entry per removed row for the life of the session. */
|
|
69
|
+
forgetRow(rowId: string): void;
|
|
70
|
+
forgetColumn(columnId: string): void;
|
|
71
|
+
/** How many cells carry formatting. For tests and diagnostics. */
|
|
72
|
+
readonly size: number;
|
|
73
|
+
serialize(): Record<string, CellFormatEntry>;
|
|
74
|
+
hydrate(entries: Record<string, CellFormatEntry>): void;
|
|
75
|
+
clearAll(): void;
|
|
76
|
+
/**
|
|
77
|
+
* Rename every row id at once; `fn` returns the new id, or null to drop
|
|
78
|
+
* the row's entries. For a store keyed on record ids this is never
|
|
79
|
+
* needed - an id travels with its record - but a SHEET keys rows by
|
|
80
|
+
* position (`r4` is the fifth row and nothing else), so inserting a row
|
|
81
|
+
* above the fifth has to move its formats to `r5`, or the band stays on
|
|
82
|
+
* row five while the cells it belonged to move down.
|
|
83
|
+
*/
|
|
84
|
+
remapRows(fn: (rowId: string) => string | null): void;
|
|
85
|
+
/** The same for column ids. */
|
|
86
|
+
remapColumns(fn: (columnId: string) => string | null): void;
|
|
87
|
+
};
|
|
88
|
+
export declare function createFormatStore(initial?: Record<string, CellFormatEntry>): SheetFormatStore;
|
|
89
|
+
/** Turn an entry into inline style text for a cell renderer. */
|
|
90
|
+
export declare function entryToStyle(entry: CellFormatEntry | undefined): string;
|
|
91
|
+
/**
|
|
92
|
+
* Cell borders as inset shadows, one per side.
|
|
93
|
+
*
|
|
94
|
+
* The cell span fills its td edge to edge, so an inset shadow along one
|
|
95
|
+
* edge is a border that costs no layout: a real border would push the text
|
|
96
|
+
* and change the cell's size, and a bordered cell next to a plain one would
|
|
97
|
+
* misalign. Shadows stack, so a cell can carry any combination of sides.
|
|
98
|
+
*/
|
|
99
|
+
export declare function borderShadows(border: CellFormatEntry['border']): string;
|