@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,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sheet document: the workbook plus everything a sheet keeps beside its
|
|
3
|
+
* cells, per sheet, in one model.
|
|
4
|
+
*
|
|
5
|
+
* The shell used to keep five Maps (format stores, column widths, row
|
|
6
|
+
* heights, hidden lines, frozen panes), each keyed by sheet name, each
|
|
7
|
+
* swapped on a sheet switch and each moved by hand on an insert or delete.
|
|
8
|
+
* Comments, validation rules, conditional formats, protection and merges
|
|
9
|
+
* would have made it ten. The document holds all of it, moves all of it in
|
|
10
|
+
* one `shift()` when rows or columns are inserted or deleted, saves and
|
|
11
|
+
* restores all of it through one `getState()` / `setState()` pair, and tells
|
|
12
|
+
* a listener once per tick what changed. `SvSheet` is a view over it: it
|
|
13
|
+
* puts the active sheet's sizes and hidden lines on the grid and writes
|
|
14
|
+
* them back here whenever they change, so the document is always the
|
|
15
|
+
* truth and the grid a rendering of it.
|
|
16
|
+
*
|
|
17
|
+
* Nothing in here touches the DOM, so a document can be built, edited,
|
|
18
|
+
* saved and restored in Node, in a test, or on a server.
|
|
19
|
+
*/
|
|
20
|
+
import { type Workbook, type SheetData } from './workbook.js';
|
|
21
|
+
import { type SheetFormatStore, type CellFormatEntry } from './format-store.js';
|
|
22
|
+
import type { FreezeState } from './freeze.js';
|
|
23
|
+
import type { StructuralEdit } from './refs.js';
|
|
24
|
+
import { type NotesMap, type Rect } from './rects.js';
|
|
25
|
+
import { type ValidationRule } from './validation.js';
|
|
26
|
+
import { type CfRule } from './conditional-formats.js';
|
|
27
|
+
import { type AutoFilterState } from './auto-filter.js';
|
|
28
|
+
/** Why the document changed. A listener gets every reason since its last call. */
|
|
29
|
+
export type SheetChangeReason = {
|
|
30
|
+
kind: 'cells';
|
|
31
|
+
} | {
|
|
32
|
+
kind: 'formats';
|
|
33
|
+
} | {
|
|
34
|
+
kind: 'sizes';
|
|
35
|
+
} | {
|
|
36
|
+
kind: 'hidden';
|
|
37
|
+
} | {
|
|
38
|
+
kind: 'freeze';
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'sheets';
|
|
41
|
+
} | {
|
|
42
|
+
kind: 'comments';
|
|
43
|
+
} | {
|
|
44
|
+
kind: 'validation';
|
|
45
|
+
} | {
|
|
46
|
+
kind: 'conditional-formats';
|
|
47
|
+
} | {
|
|
48
|
+
kind: 'protection';
|
|
49
|
+
} | {
|
|
50
|
+
kind: 'merges';
|
|
51
|
+
} | {
|
|
52
|
+
kind: 'filter';
|
|
53
|
+
} | {
|
|
54
|
+
kind: 'structure';
|
|
55
|
+
sheet: string;
|
|
56
|
+
edit: StructuralEdit;
|
|
57
|
+
} | {
|
|
58
|
+
kind: 'restore';
|
|
59
|
+
};
|
|
60
|
+
/** What one sheet keeps beside its cells. Live objects, not a snapshot. */
|
|
61
|
+
export type PerSheetState = {
|
|
62
|
+
formats: SheetFormatStore;
|
|
63
|
+
/** Column widths in px by letter; a letter absent here reads the default. */
|
|
64
|
+
widths: Record<string, number>;
|
|
65
|
+
/** Row heights in px by row index; a row absent here reads the default. */
|
|
66
|
+
heights: Map<number, number>;
|
|
67
|
+
hidden: {
|
|
68
|
+
rows: Set<number>;
|
|
69
|
+
cols: Set<number>;
|
|
70
|
+
};
|
|
71
|
+
freeze: FreezeState;
|
|
72
|
+
/** Cell comments in the grid's `notes` shape: `r4` -> `B` -> text. */
|
|
73
|
+
notes: NotesMap;
|
|
74
|
+
protected: boolean;
|
|
75
|
+
merges: Rect[];
|
|
76
|
+
/** Data validation rules, in order; the last one covering a cell applies. */
|
|
77
|
+
validation: ValidationRule[];
|
|
78
|
+
/** Conditional formatting rules, in priority order: the first one decides. */
|
|
79
|
+
conditionalFormats: CfRule[];
|
|
80
|
+
/** Excel's AutoFilter over a region, or null when the arrows are off. */
|
|
81
|
+
autoFilter: AutoFilterState | null;
|
|
82
|
+
/** The rows the AutoFilter hides right now; worked out, never saved. */
|
|
83
|
+
filterHidden: Set<number>;
|
|
84
|
+
};
|
|
85
|
+
/** One sheet's part of a saved document. JSON-safe. */
|
|
86
|
+
export type SheetStateEntry = {
|
|
87
|
+
formats: Record<string, CellFormatEntry>;
|
|
88
|
+
columnWidths: Record<string, number>;
|
|
89
|
+
rowHeights: Array<[row: number, px: number]>;
|
|
90
|
+
hidden: {
|
|
91
|
+
rows: number[];
|
|
92
|
+
cols: number[];
|
|
93
|
+
};
|
|
94
|
+
freeze: FreezeState;
|
|
95
|
+
comments: NotesMap;
|
|
96
|
+
protected: boolean;
|
|
97
|
+
merges: Array<[number, number, number, number]>;
|
|
98
|
+
validation: ValidationRule[];
|
|
99
|
+
conditionalFormats: CfRule[];
|
|
100
|
+
autoFilter: AutoFilterState | null;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* A saved document. `workbook` is the raw text of every cell (formulas as
|
|
104
|
+
* typed), the sheet order, the active sheet and the defined names; `sheets`
|
|
105
|
+
* is keyed by the sheet's spelled name.
|
|
106
|
+
*/
|
|
107
|
+
export type SheetState = {
|
|
108
|
+
version: 1;
|
|
109
|
+
workbook: ReturnType<Workbook['serialize']>;
|
|
110
|
+
sheets: Record<string, SheetStateEntry>;
|
|
111
|
+
};
|
|
112
|
+
export type SheetDocument = {
|
|
113
|
+
readonly workbook: Workbook;
|
|
114
|
+
/** The sheet's state, created on first use. Names compare case-insensitively. */
|
|
115
|
+
get(name: string): PerSheetState;
|
|
116
|
+
has(name: string): boolean;
|
|
117
|
+
rename(from: string, to: string): void;
|
|
118
|
+
remove(name: string): void;
|
|
119
|
+
/** The sheets that have state, in no particular order. */
|
|
120
|
+
names(): string[];
|
|
121
|
+
/**
|
|
122
|
+
* Move every position-keyed part of a sheet for an insert or delete:
|
|
123
|
+
* formats, heights, hidden lines, comments, merges. The workbook's own
|
|
124
|
+
* cells and formulas move through `workbook.applyStructuralEdit`; the
|
|
125
|
+
* shell calls both.
|
|
126
|
+
*/
|
|
127
|
+
shift(name: string, edit: StructuralEdit): void;
|
|
128
|
+
getState(): SheetState;
|
|
129
|
+
/**
|
|
130
|
+
* Put a saved document back. The workbook is edited in place (sheets
|
|
131
|
+
* added, removed and reordered to match, cells written where they differ,
|
|
132
|
+
* names and the active sheet set), so the workbook a consumer holds stays
|
|
133
|
+
* the same object.
|
|
134
|
+
*/
|
|
135
|
+
setState(state: SheetState): void;
|
|
136
|
+
/** Hear about changes, once per tick with every reason since the last call. */
|
|
137
|
+
subscribe(listener: (reasons: ReadonlyArray<SheetChangeReason>) => void): () => void;
|
|
138
|
+
/** Record a change. Coalesced per microtask; silent while muted. */
|
|
139
|
+
changed(reason: SheetChangeReason): void;
|
|
140
|
+
/** Run `fn` without reporting changes: restores and mount-time seeding. */
|
|
141
|
+
mute<T>(fn: () => T): T;
|
|
142
|
+
};
|
|
143
|
+
export type SheetDocumentInit = {
|
|
144
|
+
/** An existing workbook to wrap. Created from `sheets` or `state` when absent. */
|
|
145
|
+
workbook?: Workbook;
|
|
146
|
+
sheets?: ReadonlyArray<SheetData>;
|
|
147
|
+
/** A saved document to start from; wins over `sheets`. */
|
|
148
|
+
state?: SheetState;
|
|
149
|
+
};
|
|
150
|
+
export declare function createSheetDocument(init?: SheetDocumentInit): SheetDocument;
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sheet document: the workbook plus everything a sheet keeps beside its
|
|
3
|
+
* cells, per sheet, in one model.
|
|
4
|
+
*
|
|
5
|
+
* The shell used to keep five Maps (format stores, column widths, row
|
|
6
|
+
* heights, hidden lines, frozen panes), each keyed by sheet name, each
|
|
7
|
+
* swapped on a sheet switch and each moved by hand on an insert or delete.
|
|
8
|
+
* Comments, validation rules, conditional formats, protection and merges
|
|
9
|
+
* would have made it ten. The document holds all of it, moves all of it in
|
|
10
|
+
* one `shift()` when rows or columns are inserted or deleted, saves and
|
|
11
|
+
* restores all of it through one `getState()` / `setState()` pair, and tells
|
|
12
|
+
* a listener once per tick what changed. `SvSheet` is a view over it: it
|
|
13
|
+
* puts the active sheet's sizes and hidden lines on the grid and writes
|
|
14
|
+
* them back here whenever they change, so the document is always the
|
|
15
|
+
* truth and the grid a rendering of it.
|
|
16
|
+
*
|
|
17
|
+
* Nothing in here touches the DOM, so a document can be built, edited,
|
|
18
|
+
* saved and restored in Node, in a test, or on a server.
|
|
19
|
+
*/
|
|
20
|
+
import { createWorkbook } from './workbook.js';
|
|
21
|
+
import { createFormatStore } from './format-store.js';
|
|
22
|
+
import { lineShift, remapNotes, shiftRect } from './rects.js';
|
|
23
|
+
import { colToLetters, lettersToCol } from './address.js';
|
|
24
|
+
import { shiftValidation } from './validation.js';
|
|
25
|
+
import { shiftCf } from './conditional-formats.js';
|
|
26
|
+
import { shiftAutoFilter } from './auto-filter.js';
|
|
27
|
+
function emptySheetState() {
|
|
28
|
+
return {
|
|
29
|
+
formats: createFormatStore(),
|
|
30
|
+
widths: {},
|
|
31
|
+
heights: new Map(),
|
|
32
|
+
hidden: { rows: new Set(), cols: new Set() },
|
|
33
|
+
freeze: { rows: 0, cols: 0 },
|
|
34
|
+
notes: {},
|
|
35
|
+
protected: false,
|
|
36
|
+
merges: [],
|
|
37
|
+
validation: [],
|
|
38
|
+
conditionalFormats: [],
|
|
39
|
+
autoFilter: null,
|
|
40
|
+
filterHidden: new Set(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function createSheetDocument(init = {}) {
|
|
44
|
+
const workbook = init.workbook ??
|
|
45
|
+
createWorkbook(init.state ? init.state.workbook.sheets.map((s) => ({ name: s.name, cells: s.cells })) : (init.sheets ? [...init.sheets] : [{ name: 'Sheet1', cells: [] }]));
|
|
46
|
+
const entries = new Map();
|
|
47
|
+
const listeners = new Set();
|
|
48
|
+
let pending = [];
|
|
49
|
+
let scheduled = false;
|
|
50
|
+
let muted = 0;
|
|
51
|
+
const key = (name) => name.toLowerCase();
|
|
52
|
+
function get(name) {
|
|
53
|
+
let found = entries.get(key(name));
|
|
54
|
+
if (!found) {
|
|
55
|
+
found = emptySheetState();
|
|
56
|
+
entries.set(key(name), found);
|
|
57
|
+
}
|
|
58
|
+
return found;
|
|
59
|
+
}
|
|
60
|
+
function flush() {
|
|
61
|
+
scheduled = false;
|
|
62
|
+
const batch = pending;
|
|
63
|
+
pending = [];
|
|
64
|
+
for (const listener of listeners)
|
|
65
|
+
listener(batch);
|
|
66
|
+
}
|
|
67
|
+
function changed(reason) {
|
|
68
|
+
if (muted > 0)
|
|
69
|
+
return;
|
|
70
|
+
// The same plain reason twice in a tick says nothing new; a structural
|
|
71
|
+
// edit is worth hearing about each time.
|
|
72
|
+
if (reason.kind !== 'structure' && pending.some((r) => r.kind === reason.kind))
|
|
73
|
+
return;
|
|
74
|
+
pending.push(reason);
|
|
75
|
+
if (scheduled)
|
|
76
|
+
return;
|
|
77
|
+
scheduled = true;
|
|
78
|
+
queueMicrotask(flush);
|
|
79
|
+
}
|
|
80
|
+
function serializeEntry(state) {
|
|
81
|
+
return {
|
|
82
|
+
formats: state.formats.serialize(),
|
|
83
|
+
columnWidths: { ...state.widths },
|
|
84
|
+
rowHeights: [...state.heights].map(([r, h]) => [r, h]),
|
|
85
|
+
hidden: { rows: [...state.hidden.rows], cols: [...state.hidden.cols] },
|
|
86
|
+
freeze: { ...state.freeze },
|
|
87
|
+
comments: Object.fromEntries(Object.entries(state.notes).map(([r, line]) => [r, { ...line }])),
|
|
88
|
+
protected: state.protected,
|
|
89
|
+
merges: state.merges.map(([r1, c1, r2, c2]) => [r1, c1, r2, c2]),
|
|
90
|
+
validation: state.validation.map((rule) => ({ ...rule, rects: rule.rects.map((r) => [...r]), alert: { ...rule.alert } })),
|
|
91
|
+
conditionalFormats: state.conditionalFormats.map((rule) => ({ ...rule, rects: rule.rects.map((r) => [...r]) })),
|
|
92
|
+
autoFilter: state.autoFilter ? { range: [...state.autoFilter.range], filters: { ...state.autoFilter.filters } } : null,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function hydrateEntry(state, entry) {
|
|
96
|
+
state.formats.hydrate(entry.formats ?? {});
|
|
97
|
+
state.widths = { ...(entry.columnWidths ?? {}) };
|
|
98
|
+
state.heights = new Map(entry.rowHeights ?? []);
|
|
99
|
+
state.hidden = { rows: new Set(entry.hidden?.rows ?? []), cols: new Set(entry.hidden?.cols ?? []) };
|
|
100
|
+
state.freeze = { rows: entry.freeze?.rows ?? 0, cols: entry.freeze?.cols ?? 0 };
|
|
101
|
+
state.notes = Object.fromEntries(Object.entries(entry.comments ?? {}).map(([r, line]) => [r, { ...line }]));
|
|
102
|
+
state.protected = entry.protected ?? false;
|
|
103
|
+
state.merges = (entry.merges ?? []).map(([r1, c1, r2, c2]) => [r1, c1, r2, c2]);
|
|
104
|
+
state.validation = (entry.validation ?? []).map((rule) => ({
|
|
105
|
+
...rule,
|
|
106
|
+
rects: rule.rects.map(([r1, c1, r2, c2]) => [r1, c1, r2, c2]),
|
|
107
|
+
alert: { ...rule.alert },
|
|
108
|
+
}));
|
|
109
|
+
state.conditionalFormats = (entry.conditionalFormats ?? []).map((rule) => ({
|
|
110
|
+
...rule,
|
|
111
|
+
rects: rule.rects.map(([r1, c1, r2, c2]) => [r1, c1, r2, c2]),
|
|
112
|
+
}));
|
|
113
|
+
const af = entry.autoFilter;
|
|
114
|
+
state.autoFilter = af ? { range: [af.range[0], af.range[1], af.range[2], af.range[3]], filters: { ...af.filters } } : null;
|
|
115
|
+
state.filterHidden = new Set();
|
|
116
|
+
}
|
|
117
|
+
const document = {
|
|
118
|
+
workbook,
|
|
119
|
+
get,
|
|
120
|
+
has: (name) => entries.has(key(name)),
|
|
121
|
+
rename(from, to) {
|
|
122
|
+
const moved = entries.get(key(from));
|
|
123
|
+
entries.delete(key(from));
|
|
124
|
+
if (moved)
|
|
125
|
+
entries.set(key(to), moved);
|
|
126
|
+
},
|
|
127
|
+
remove(name) {
|
|
128
|
+
entries.delete(key(name));
|
|
129
|
+
},
|
|
130
|
+
names: () => [...entries.keys()],
|
|
131
|
+
shift(name, edit) {
|
|
132
|
+
const state = get(name);
|
|
133
|
+
const shift = lineShift(edit);
|
|
134
|
+
const rows = edit.kind === 'insertRows' || edit.kind === 'deleteRows';
|
|
135
|
+
if (rows) {
|
|
136
|
+
state.formats.remapRows((id) => {
|
|
137
|
+
const i = Number(id.slice(1));
|
|
138
|
+
const next = Number.isInteger(i) ? shift(i) : i;
|
|
139
|
+
return next === null ? null : `r${next}`;
|
|
140
|
+
});
|
|
141
|
+
const heights = new Map();
|
|
142
|
+
for (const [r, h] of state.heights) {
|
|
143
|
+
const next = shift(r);
|
|
144
|
+
if (next !== null)
|
|
145
|
+
heights.set(next, h);
|
|
146
|
+
}
|
|
147
|
+
state.heights = heights;
|
|
148
|
+
const hiddenRows = new Set();
|
|
149
|
+
for (const r of state.hidden.rows) {
|
|
150
|
+
const next = shift(r);
|
|
151
|
+
if (next !== null)
|
|
152
|
+
hiddenRows.add(next);
|
|
153
|
+
}
|
|
154
|
+
state.hidden = { rows: hiddenRows, cols: state.hidden.cols };
|
|
155
|
+
state.notes = remapNotes(state.notes, 'rows', shift);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
state.formats.remapColumns((id) => {
|
|
159
|
+
const i = lettersToCol(id);
|
|
160
|
+
const next = i < 0 ? i : shift(i);
|
|
161
|
+
return next === null ? null : colToLetters(next);
|
|
162
|
+
});
|
|
163
|
+
const widths = {};
|
|
164
|
+
for (const [letter, w] of Object.entries(state.widths)) {
|
|
165
|
+
const i = lettersToCol(letter);
|
|
166
|
+
const next = i < 0 ? i : shift(i);
|
|
167
|
+
if (next !== null)
|
|
168
|
+
widths[i < 0 ? letter : colToLetters(next)] = w;
|
|
169
|
+
}
|
|
170
|
+
state.widths = widths;
|
|
171
|
+
const hiddenCols = new Set();
|
|
172
|
+
for (const c of state.hidden.cols) {
|
|
173
|
+
const next = shift(c);
|
|
174
|
+
if (next !== null)
|
|
175
|
+
hiddenCols.add(next);
|
|
176
|
+
}
|
|
177
|
+
state.hidden = { rows: state.hidden.rows, cols: hiddenCols };
|
|
178
|
+
state.notes = remapNotes(state.notes, 'cols', shift);
|
|
179
|
+
}
|
|
180
|
+
state.merges = state.merges.map((m) => shiftRect(m, edit)).filter((m) => m !== null);
|
|
181
|
+
state.validation = shiftValidation(state.validation, edit);
|
|
182
|
+
state.conditionalFormats = shiftCf(state.conditionalFormats, edit);
|
|
183
|
+
state.autoFilter = shiftAutoFilter(state.autoFilter, edit);
|
|
184
|
+
},
|
|
185
|
+
getState() {
|
|
186
|
+
const sheets = {};
|
|
187
|
+
for (const name of workbook.sheets)
|
|
188
|
+
sheets[name] = serializeEntry(get(name));
|
|
189
|
+
return { version: 1, workbook: workbook.serialize(), sheets };
|
|
190
|
+
},
|
|
191
|
+
setState(state) {
|
|
192
|
+
document.mute(() => {
|
|
193
|
+
const wanted = state.workbook.sheets.map((s) => s.name);
|
|
194
|
+
const wantedKeys = new Set(wanted.map(key));
|
|
195
|
+
// Sheets to add, then the ones to drop (never the last one standing),
|
|
196
|
+
// then the order, then the cells.
|
|
197
|
+
for (const name of wanted) {
|
|
198
|
+
if (!workbook.sheets.some((s) => key(s) === key(name)))
|
|
199
|
+
workbook.addSheet(name);
|
|
200
|
+
}
|
|
201
|
+
for (const name of [...workbook.sheets]) {
|
|
202
|
+
if (!wantedKeys.has(key(name)) && workbook.sheets.length > 1) {
|
|
203
|
+
workbook.removeSheet(name);
|
|
204
|
+
entries.delete(key(name));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
wanted.forEach((name, index) => {
|
|
208
|
+
if (workbook.sheets[index]?.toLowerCase() !== key(name))
|
|
209
|
+
workbook.moveSheet(name, index);
|
|
210
|
+
});
|
|
211
|
+
for (const sheet of state.workbook.sheets) {
|
|
212
|
+
const rows = Math.max(workbook.rowCount(sheet.name), sheet.cells.length);
|
|
213
|
+
for (let r = 0; r < rows; r += 1) {
|
|
214
|
+
const cols = Math.max(workbook.colCount(sheet.name), sheet.cells[r]?.length ?? 0);
|
|
215
|
+
for (let c = 0; c < cols; c += 1) {
|
|
216
|
+
const text = sheet.cells[r]?.[c] ?? '';
|
|
217
|
+
if (workbook.getRaw(sheet.name, r, c) !== text)
|
|
218
|
+
workbook.setRaw(sheet.name, r, c, text);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
workbook.names.hydrate(state.workbook.names ?? {});
|
|
223
|
+
if (workbook.sheets.some((s) => key(s) === key(state.workbook.active)))
|
|
224
|
+
workbook.setActive(state.workbook.active);
|
|
225
|
+
// Entries are hydrated in place, never replaced: a format store the
|
|
226
|
+
// shell has registered as its target must stay the store in use, or
|
|
227
|
+
// every format written after a restore would land in an orphan.
|
|
228
|
+
const kept = new Set(Object.keys(state.sheets ?? {}).map(key));
|
|
229
|
+
for (const name of [...entries.keys()])
|
|
230
|
+
if (!kept.has(name) && !wantedKeys.has(name))
|
|
231
|
+
entries.delete(name);
|
|
232
|
+
for (const name of workbook.sheets)
|
|
233
|
+
hydrateEntry(get(name), state.sheets?.[name] ?? {});
|
|
234
|
+
});
|
|
235
|
+
changed({ kind: 'restore' });
|
|
236
|
+
},
|
|
237
|
+
subscribe(listener) {
|
|
238
|
+
listeners.add(listener);
|
|
239
|
+
return () => {
|
|
240
|
+
listeners.delete(listener);
|
|
241
|
+
};
|
|
242
|
+
},
|
|
243
|
+
changed,
|
|
244
|
+
mute(fn) {
|
|
245
|
+
muted += 1;
|
|
246
|
+
try {
|
|
247
|
+
return fn();
|
|
248
|
+
}
|
|
249
|
+
finally {
|
|
250
|
+
muted -= 1;
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
if (init.state) {
|
|
255
|
+
document.mute(() => {
|
|
256
|
+
workbook.names.hydrate(init.state.workbook.names ?? {});
|
|
257
|
+
if (workbook.sheets.some((s) => key(s) === key(init.state.workbook.active)))
|
|
258
|
+
workbook.setActive(init.state.workbook.active);
|
|
259
|
+
for (const [name, entry] of Object.entries(init.state.sheets ?? {}))
|
|
260
|
+
hydrateEntry(get(name), entry);
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
return document;
|
|
264
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keys that act on the text of a formula while it is being written.
|
|
3
|
+
*
|
|
4
|
+
* F4 in Excel cycles the reference at the caret through its four anchorings:
|
|
5
|
+
* `A1` -> `$A$1` -> `A$1` -> `$A1` -> `A1`, both ends of a range together.
|
|
6
|
+
* The caret counts as "at" a reference when it sits inside it or right after
|
|
7
|
+
* it, which is where it is after typing one. A sheet prefix (`Orders!A1`)
|
|
8
|
+
* is left as it is; only the cell part turns.
|
|
9
|
+
*/
|
|
10
|
+
export type TextEdit = {
|
|
11
|
+
text: string;
|
|
12
|
+
caret: number;
|
|
13
|
+
};
|
|
14
|
+
export declare function cycleReference(text: string, caret: number): TextEdit | null;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const CELL = String.raw `\$?[A-Za-z]{1,3}\$?\d+`;
|
|
2
|
+
const REFERENCE = new RegExp(String.raw `(${CELL})(?::(${CELL}))?`, 'g');
|
|
3
|
+
/** The next anchoring in Excel's F4 order. */
|
|
4
|
+
function turn(cell) {
|
|
5
|
+
const m = /^(\$?)([A-Za-z]+)(\$?)(\d+)$/.exec(cell);
|
|
6
|
+
if (!m)
|
|
7
|
+
return cell;
|
|
8
|
+
const [, colAbs, col, rowAbs, row] = m;
|
|
9
|
+
const state = (colAbs ? 2 : 0) + (rowAbs ? 1 : 0);
|
|
10
|
+
// relative -> both -> row only -> column only -> relative
|
|
11
|
+
const next = { 0: 3, 3: 1, 1: 2, 2: 0 }[state];
|
|
12
|
+
return `${next & 2 ? '$' : ''}${col}${next & 1 ? '$' : ''}${row}`;
|
|
13
|
+
}
|
|
14
|
+
export function cycleReference(text, caret) {
|
|
15
|
+
if (!text.startsWith('='))
|
|
16
|
+
return null;
|
|
17
|
+
REFERENCE.lastIndex = 0;
|
|
18
|
+
let m;
|
|
19
|
+
while ((m = REFERENCE.exec(text)) !== null) {
|
|
20
|
+
const start = m.index;
|
|
21
|
+
const end = start + m[0].length;
|
|
22
|
+
if (caret < start || caret > end)
|
|
23
|
+
continue;
|
|
24
|
+
// A word character before the match means this is part of a name or a
|
|
25
|
+
// function (SUM1 is not a cell), not a reference.
|
|
26
|
+
const before = text[start - 1];
|
|
27
|
+
if (before && /[A-Za-z0-9_.]/.test(before))
|
|
28
|
+
continue;
|
|
29
|
+
// And a parenthesis after it makes it a function call: SUM1 is a cell in
|
|
30
|
+
// Excel's grid, SUM1( is not.
|
|
31
|
+
if (text[end] === '(')
|
|
32
|
+
continue;
|
|
33
|
+
const replaced = m[2] ? `${turn(m[1])}:${turn(m[2])}` : turn(m[1]);
|
|
34
|
+
return {
|
|
35
|
+
text: text.slice(0, start) + replaced + text.slice(end),
|
|
36
|
+
caret: start + replaced.length,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What Excel makes of a typed entry before it is stored.
|
|
3
|
+
*
|
|
4
|
+
* `12%` is the number 0.12 shown as a percentage, `$1,200` is 1200 shown
|
|
5
|
+
* as currency, `1,234.5` is 1234.5 with a thousands separator: the text
|
|
6
|
+
* the user typed names both a value and how to show it, and the cell keeps
|
|
7
|
+
* both, so `=A1*2` over a `12%` cell is 0.24 rather than `#VALUE!`. The
|
|
8
|
+
* format is the one Excel would pick, with the decimals the user typed.
|
|
9
|
+
* Anything else, formulas included, is stored as typed.
|
|
10
|
+
*/
|
|
11
|
+
export type ParsedEntry = {
|
|
12
|
+
/** The number, as text the workbook stores. */
|
|
13
|
+
value: string;
|
|
14
|
+
/** The Excel format string the entry implies. */
|
|
15
|
+
numFmt: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Excel's AutoComplete for a column of text.
|
|
19
|
+
*
|
|
20
|
+
* As a cell is typed into, the entries in the same column that run without
|
|
21
|
+
* a blank above and below it are the candidates; when exactly one distinct
|
|
22
|
+
* text entry starts with what was typed, that entry is the completion, in
|
|
23
|
+
* its own case ("jo" completes to "John Smith"). Two candidates that both
|
|
24
|
+
* fit ("Apple", "Apricot" for "Ap") complete nothing until the typing tells
|
|
25
|
+
* them apart. Numbers, formulas and booleans never complete, and a typed
|
|
26
|
+
* text that already is an entry needs none.
|
|
27
|
+
*
|
|
28
|
+
* `entries` are the distinct text entries of the run, any order.
|
|
29
|
+
*/
|
|
30
|
+
export declare function completeEntry(typed: string, entries: Iterable<string>): string | null;
|
|
31
|
+
export declare function parseEntry(text: string): ParsedEntry | null;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const zeros = (n) => (n > 0 ? '.' + '0'.repeat(n) : '');
|
|
2
|
+
/** Digits with optional thousands separators and decimals, as a number. */
|
|
3
|
+
function numberOf(digits) {
|
|
4
|
+
const plain = digits.replace(/,/g, '');
|
|
5
|
+
const n = Number(plain);
|
|
6
|
+
return Number.isFinite(n) ? n : null;
|
|
7
|
+
}
|
|
8
|
+
const decimalsOf = (digits) => digits.split('.')[1]?.length ?? 0;
|
|
9
|
+
/** Round away the binary noise of a division: 33.3% is 0.333, not 0.33299999. */
|
|
10
|
+
const trim = (n, places) => String(Number(n.toFixed(places)));
|
|
11
|
+
const GROUPED = String.raw `(?:\d{1,3}(?:,\d{3})+|\d+)(?:\.\d+)?`;
|
|
12
|
+
const PERCENT = new RegExp(String.raw `^(-?)(${GROUPED})\s?%$`);
|
|
13
|
+
const CURRENCY = new RegExp(String.raw `^(-?)\$\s?(${GROUPED})$`);
|
|
14
|
+
const CURRENCY_PAREN = new RegExp(String.raw `^\(\$\s?(${GROUPED})\)$`);
|
|
15
|
+
const THOUSANDS = new RegExp(String.raw `^(-?)(\d{1,3}(?:,\d{3})+(?:\.\d+)?)$`);
|
|
16
|
+
/**
|
|
17
|
+
* Excel's AutoComplete for a column of text.
|
|
18
|
+
*
|
|
19
|
+
* As a cell is typed into, the entries in the same column that run without
|
|
20
|
+
* a blank above and below it are the candidates; when exactly one distinct
|
|
21
|
+
* text entry starts with what was typed, that entry is the completion, in
|
|
22
|
+
* its own case ("jo" completes to "John Smith"). Two candidates that both
|
|
23
|
+
* fit ("Apple", "Apricot" for "Ap") complete nothing until the typing tells
|
|
24
|
+
* them apart. Numbers, formulas and booleans never complete, and a typed
|
|
25
|
+
* text that already is an entry needs none.
|
|
26
|
+
*
|
|
27
|
+
* `entries` are the distinct text entries of the run, any order.
|
|
28
|
+
*/
|
|
29
|
+
export function completeEntry(typed, entries) {
|
|
30
|
+
if (typed === '' || typed.startsWith('=') || typed.includes('\n'))
|
|
31
|
+
return null;
|
|
32
|
+
const lower = typed.toLowerCase();
|
|
33
|
+
let found = null;
|
|
34
|
+
for (const entry of entries) {
|
|
35
|
+
if (entry.length <= typed.length || !entry.toLowerCase().startsWith(lower))
|
|
36
|
+
continue;
|
|
37
|
+
if (found !== null && found !== entry)
|
|
38
|
+
return null;
|
|
39
|
+
found = entry;
|
|
40
|
+
}
|
|
41
|
+
return found;
|
|
42
|
+
}
|
|
43
|
+
export function parseEntry(text) {
|
|
44
|
+
const t = text.trim();
|
|
45
|
+
if (t === '' || t.startsWith('='))
|
|
46
|
+
return null;
|
|
47
|
+
let m = PERCENT.exec(t);
|
|
48
|
+
if (m) {
|
|
49
|
+
const n = numberOf(m[2]);
|
|
50
|
+
if (n === null)
|
|
51
|
+
return null;
|
|
52
|
+
const places = decimalsOf(m[2]);
|
|
53
|
+
return { value: trim((m[1] ? -n : n) / 100, places + 2), numFmt: `0${zeros(places)}%` };
|
|
54
|
+
}
|
|
55
|
+
m = CURRENCY.exec(t);
|
|
56
|
+
const paren = m ? null : CURRENCY_PAREN.exec(t);
|
|
57
|
+
if (m || paren) {
|
|
58
|
+
const digits = (m ? m[2] : paren[1]);
|
|
59
|
+
const n = numberOf(digits);
|
|
60
|
+
if (n === null)
|
|
61
|
+
return null;
|
|
62
|
+
const negative = m ? m[1] === '-' : true;
|
|
63
|
+
const places = decimalsOf(digits) > 0 ? 2 : 0;
|
|
64
|
+
const body = `$#,##0${zeros(places)}`;
|
|
65
|
+
return { value: String(negative ? -n : n), numFmt: `${body};(${body})` };
|
|
66
|
+
}
|
|
67
|
+
m = THOUSANDS.exec(t);
|
|
68
|
+
if (m) {
|
|
69
|
+
const n = numberOf(m[2]);
|
|
70
|
+
if (n === null)
|
|
71
|
+
return null;
|
|
72
|
+
const places = decimalsOf(m[2]);
|
|
73
|
+
return { value: String(m[1] ? -n : n), numFmt: `#,##0${zeros(places)}` };
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
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 { type CellValue, type Node } from './ast.js';
|
|
17
|
+
import { type SheetFunction } from './functions.js';
|
|
18
|
+
import { type TableRegion } from './tables.js';
|
|
19
|
+
export type EvalContext = {
|
|
20
|
+
/** Read one cell. Out of bounds should return `{ error: '#REF!' }`. */
|
|
21
|
+
resolve(sheet: string | null, row: number, col: number): CellValue;
|
|
22
|
+
/** Last used row of a sheet, for open-ended whole-column ranges. */
|
|
23
|
+
lastRow(sheet: string | null): number;
|
|
24
|
+
/**
|
|
25
|
+
* Resolve a defined name to the reference it stands for, or null when
|
|
26
|
+
* there is no such name. The node is evaluated in place of the name, so a
|
|
27
|
+
* name that refers to a range behaves as that range: `=SUM(Sales)` adds
|
|
28
|
+
* the whole column and `=VLOOKUP(x, Prices, 2)` searches the whole table.
|
|
29
|
+
* Preferred over `resolveName`, which can only ever produce one value.
|
|
30
|
+
*/
|
|
31
|
+
resolveNameNode?(name: string): Node | null;
|
|
32
|
+
/** Resolve a defined name to a single value. Consulted when
|
|
33
|
+
* `resolveNameNode` is absent or returns null; undefined is #NAME?. */
|
|
34
|
+
resolveName?(name: string): CellValue | undefined;
|
|
35
|
+
functions?: Record<string, SheetFunction>;
|
|
36
|
+
/** Look a table up by name. */
|
|
37
|
+
findTable?(name: string): TableRegion | undefined;
|
|
38
|
+
/** The table containing the cell the formula lives in, which is the only
|
|
39
|
+
* way the unqualified `[@Amount]` form can mean anything. */
|
|
40
|
+
tableAt?(sheet: string | null, row: number, col: number): TableRegion | undefined;
|
|
41
|
+
/** Where the formula being evaluated sits. Needed for `[@Column]`, which
|
|
42
|
+
* is relative to the formula rather than to the table. */
|
|
43
|
+
currentCell?: {
|
|
44
|
+
sheet: string | null;
|
|
45
|
+
row: number;
|
|
46
|
+
col: number;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Evaluate a parsed formula. Never throws: a `FormulaError` raised anywhere
|
|
51
|
+
* inside becomes the matching error value.
|
|
52
|
+
*/
|
|
53
|
+
/**
|
|
54
|
+
* The values a node stands for as a grid: a range, a reference (one cell),
|
|
55
|
+
* or a name that refers to either. Null for anything else, which is how a
|
|
56
|
+
* caller tells "a list source that is a range" from "a formula".
|
|
57
|
+
*/
|
|
58
|
+
export declare function rangeValues(node: Node, ctx: EvalContext): CellValue[][] | null;
|
|
59
|
+
export declare function evaluate(node: Node, ctx: EvalContext): CellValue;
|
|
60
|
+
/** Render a computed value the way a cell shows it. */
|
|
61
|
+
export declare function formatValue(v: CellValue): string;
|