@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,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rectangles of cells, and how they move when rows or columns are inserted
|
|
3
|
+
* or deleted.
|
|
4
|
+
*
|
|
5
|
+
* A sheet keys more than its formats by position: validation rules,
|
|
6
|
+
* conditional formats, merges and an AutoFilter range are all rectangles,
|
|
7
|
+
* and every one of them has to move with the cells it covers, the way the
|
|
8
|
+
* format store's entries do. This is the one place that rule lives, so a
|
|
9
|
+
* band on rows 5-8 and a rule on rows 5-8 cannot disagree about where an
|
|
10
|
+
* insert above them put them.
|
|
11
|
+
*/
|
|
12
|
+
import type { Rect } from './navigate.js';
|
|
13
|
+
import type { StructuralEdit } from './refs.js';
|
|
14
|
+
export type { Rect };
|
|
15
|
+
/**
|
|
16
|
+
* Where `rect` is after `edit`, or null when the edit deleted all of it.
|
|
17
|
+
*
|
|
18
|
+
* An insert above a range moves it; an insert inside it grows it, the way
|
|
19
|
+
* a reference to the range would grow. A delete above it moves it up, a
|
|
20
|
+
* delete overlapping an edge shrinks it to what survives, and a delete of
|
|
21
|
+
* every line it covers removes it.
|
|
22
|
+
*/
|
|
23
|
+
export declare function shiftRect(rect: Rect, edit: StructuralEdit): Rect | null;
|
|
24
|
+
/** Every item's rectangles moved; an item left with none is dropped. */
|
|
25
|
+
export declare function shiftRects<T extends {
|
|
26
|
+
rects: ReadonlyArray<Rect>;
|
|
27
|
+
}>(items: ReadonlyArray<T>, edit: StructuralEdit): T[];
|
|
28
|
+
export declare function rectContains(rect: Rect, row: number, col: number): boolean;
|
|
29
|
+
export declare function rectsIntersect(a: Rect, b: Rect): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* `rect` with `hole` cut out of it: up to four rectangles (above, below,
|
|
32
|
+
* left and right of the hole), or the rect itself when they do not meet.
|
|
33
|
+
* Clear Rules on a selection takes the selection out of every rule this way.
|
|
34
|
+
*/
|
|
35
|
+
export declare function subtractRect(rect: Rect, hole: Rect): Rect[];
|
|
36
|
+
/** The grid's notes shape: row id (`r4`) to column id (`B`) to text. */
|
|
37
|
+
export type NotesMap = Record<string, Record<string, string>>;
|
|
38
|
+
/**
|
|
39
|
+
* Notes moved along with their rows or columns. `shift` says where line i
|
|
40
|
+
* is after the edit, or null when the edit deleted it, which drops its
|
|
41
|
+
* notes with it.
|
|
42
|
+
*/
|
|
43
|
+
export declare function remapNotes(notes: NotesMap, axis: 'rows' | 'cols', shift: (index: number) => number | null): NotesMap;
|
|
44
|
+
/** The shift function a structural edit implies, shared by every keyed part. */
|
|
45
|
+
export declare function lineShift(edit: StructuralEdit): (index: number) => number | null;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { colToLetters, lettersToCol } from './address.js';
|
|
2
|
+
/**
|
|
3
|
+
* Where `rect` is after `edit`, or null when the edit deleted all of it.
|
|
4
|
+
*
|
|
5
|
+
* An insert above a range moves it; an insert inside it grows it, the way
|
|
6
|
+
* a reference to the range would grow. A delete above it moves it up, a
|
|
7
|
+
* delete overlapping an edge shrinks it to what survives, and a delete of
|
|
8
|
+
* every line it covers removes it.
|
|
9
|
+
*/
|
|
10
|
+
export function shiftRect(rect, edit) {
|
|
11
|
+
const [r1, c1, r2, c2] = rect;
|
|
12
|
+
const { at, count } = edit;
|
|
13
|
+
const rows = edit.kind === 'insertRows' || edit.kind === 'deleteRows';
|
|
14
|
+
const lo = rows ? r1 : c1;
|
|
15
|
+
const hi = rows ? r2 : c2;
|
|
16
|
+
let nlo;
|
|
17
|
+
let nhi;
|
|
18
|
+
if (edit.kind === 'insertRows' || edit.kind === 'insertCols') {
|
|
19
|
+
nlo = lo >= at ? lo + count : lo;
|
|
20
|
+
nhi = hi >= at ? hi + count : hi;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
const end = at + count;
|
|
24
|
+
if (lo >= at && hi < end)
|
|
25
|
+
return null;
|
|
26
|
+
nlo = lo >= end ? lo - count : lo >= at ? at : lo;
|
|
27
|
+
nhi = hi >= end ? hi - count : hi >= at ? at - 1 : hi;
|
|
28
|
+
}
|
|
29
|
+
if (nhi < nlo)
|
|
30
|
+
return null;
|
|
31
|
+
return rows ? [nlo, c1, nhi, c2] : [r1, nlo, r2, nhi];
|
|
32
|
+
}
|
|
33
|
+
/** Every item's rectangles moved; an item left with none is dropped. */
|
|
34
|
+
export function shiftRects(items, edit) {
|
|
35
|
+
const out = [];
|
|
36
|
+
for (const item of items) {
|
|
37
|
+
const rects = [];
|
|
38
|
+
for (const rect of item.rects) {
|
|
39
|
+
const moved = shiftRect(rect, edit);
|
|
40
|
+
if (moved)
|
|
41
|
+
rects.push(moved);
|
|
42
|
+
}
|
|
43
|
+
if (rects.length)
|
|
44
|
+
out.push({ ...item, rects });
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
export function rectContains(rect, row, col) {
|
|
49
|
+
return row >= rect[0] && row <= rect[2] && col >= rect[1] && col <= rect[3];
|
|
50
|
+
}
|
|
51
|
+
export function rectsIntersect(a, b) {
|
|
52
|
+
return a[0] <= b[2] && b[0] <= a[2] && a[1] <= b[3] && b[1] <= a[3];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* `rect` with `hole` cut out of it: up to four rectangles (above, below,
|
|
56
|
+
* left and right of the hole), or the rect itself when they do not meet.
|
|
57
|
+
* Clear Rules on a selection takes the selection out of every rule this way.
|
|
58
|
+
*/
|
|
59
|
+
export function subtractRect(rect, hole) {
|
|
60
|
+
if (!rectsIntersect(rect, hole))
|
|
61
|
+
return [rect];
|
|
62
|
+
const [r1, c1, r2, c2] = rect;
|
|
63
|
+
const [h1, hc1, h2, hc2] = hole;
|
|
64
|
+
const out = [];
|
|
65
|
+
if (h1 > r1)
|
|
66
|
+
out.push([r1, c1, h1 - 1, c2]);
|
|
67
|
+
if (h2 < r2)
|
|
68
|
+
out.push([h2 + 1, c1, r2, c2]);
|
|
69
|
+
const top = Math.max(r1, h1);
|
|
70
|
+
const bottom = Math.min(r2, h2);
|
|
71
|
+
if (hc1 > c1)
|
|
72
|
+
out.push([top, c1, bottom, hc1 - 1]);
|
|
73
|
+
if (hc2 < c2)
|
|
74
|
+
out.push([top, hc2 + 1, bottom, c2]);
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Notes moved along with their rows or columns. `shift` says where line i
|
|
79
|
+
* is after the edit, or null when the edit deleted it, which drops its
|
|
80
|
+
* notes with it.
|
|
81
|
+
*/
|
|
82
|
+
export function remapNotes(notes, axis, shift) {
|
|
83
|
+
const out = {};
|
|
84
|
+
for (const [rowId, line] of Object.entries(notes)) {
|
|
85
|
+
if (axis === 'rows') {
|
|
86
|
+
const index = Number(rowId.slice(1));
|
|
87
|
+
const next = Number.isInteger(index) ? shift(index) : index;
|
|
88
|
+
if (next === null)
|
|
89
|
+
continue;
|
|
90
|
+
out[`r${next}`] = { ...line };
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const moved = {};
|
|
94
|
+
for (const [columnId, text] of Object.entries(line)) {
|
|
95
|
+
const index = lettersToCol(columnId);
|
|
96
|
+
const next = index < 0 ? index : shift(index);
|
|
97
|
+
if (next === null)
|
|
98
|
+
continue;
|
|
99
|
+
moved[index < 0 ? columnId : colToLetters(next)] = text;
|
|
100
|
+
}
|
|
101
|
+
if (Object.keys(moved).length)
|
|
102
|
+
out[rowId] = moved;
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
/** The shift function a structural edit implies, shared by every keyed part. */
|
|
107
|
+
export function lineShift(edit) {
|
|
108
|
+
const { at, count } = edit;
|
|
109
|
+
if (edit.kind === 'insertRows' || edit.kind === 'insertCols')
|
|
110
|
+
return (i) => (i >= at ? i + count : i);
|
|
111
|
+
return (i) => (i >= at + count ? i - count : i >= at ? null : i);
|
|
112
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reference rewriting. The one module allowed to move a reference.
|
|
3
|
+
*
|
|
4
|
+
* Two jobs, both of which the demo engines could not do because they discarded
|
|
5
|
+
* `$` at parse time:
|
|
6
|
+
*
|
|
7
|
+
* - `translateFormula` shifts RELATIVE parts by a delta and leaves pinned
|
|
8
|
+
* parts alone. This is what makes fill-down, copy/paste and drag-fill
|
|
9
|
+
* correct: `=$A$1*B2` filled one row down becomes `=$A$1*B3`.
|
|
10
|
+
* - `fixupReferences` rewrites every formula in a sheet after rows or columns
|
|
11
|
+
* are inserted or removed. A reference to a deleted cell becomes `#REF!`;
|
|
12
|
+
* everything past the edit shifts; a range straddling an insertion grows.
|
|
13
|
+
*
|
|
14
|
+
* Both re-serialise from the AST rather than editing text, so `SUM( A1 : B2 )`
|
|
15
|
+
* comes back normalised instead of half-rewritten.
|
|
16
|
+
*/
|
|
17
|
+
import { type Node } from './ast.js';
|
|
18
|
+
/** Serialise an AST back to formula text, with the leading `=`. */
|
|
19
|
+
export declare function formatFormula(node: Node): string;
|
|
20
|
+
/**
|
|
21
|
+
* Shift every relative reference in a formula by (dRow, dCol).
|
|
22
|
+
*
|
|
23
|
+
* Anything that is not a formula comes back untouched, so callers can pass a
|
|
24
|
+
* whole column of mixed literals and formulas through it.
|
|
25
|
+
*/
|
|
26
|
+
export declare function translateFormula(text: unknown, dRow: number, dCol: number): unknown;
|
|
27
|
+
/**
|
|
28
|
+
* Point every reference that names sheet `from` at `to` instead: what a
|
|
29
|
+
* sheet rename owes the formulas on the other sheets and the defined
|
|
30
|
+
* names, and what Excel does on one. Parsed and re-rendered rather than
|
|
31
|
+
* substituted as text, so a string literal that happens to contain the old
|
|
32
|
+
* name is left alone and a new name with a space comes out quoted. A
|
|
33
|
+
* formula that names no such sheet is returned as it was, untouched.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* Excel's reference colours: the A1 references in a formula being typed,
|
|
37
|
+
* each occurrence with the colour of its range, so the cells a formula
|
|
38
|
+
* names and the text that names them can be painted alike. Colours go by
|
|
39
|
+
* distinct range in order of first appearance, so `B5` twice is one
|
|
40
|
+
* colour; `$` anchors and case do not tell ranges apart; a reference into
|
|
41
|
+
* another sheet and anything inside a string are skipped. `rect` is
|
|
42
|
+
* [row1, col1, row2, col2], zero-based and normalised.
|
|
43
|
+
*/
|
|
44
|
+
export type ReferenceSpan = {
|
|
45
|
+
start: number;
|
|
46
|
+
end: number;
|
|
47
|
+
key: string;
|
|
48
|
+
colour: string;
|
|
49
|
+
rect: readonly [number, number, number, number];
|
|
50
|
+
};
|
|
51
|
+
export declare const REFERENCE_COLOURS: ReadonlyArray<string>;
|
|
52
|
+
export declare function referenceSpans(text: string, colours?: ReadonlyArray<string>): ReferenceSpan[];
|
|
53
|
+
export declare function renameSheetReferences(text: unknown, from: string, to: string): unknown;
|
|
54
|
+
export type StructuralEdit = {
|
|
55
|
+
kind: 'insertRows' | 'deleteRows' | 'insertCols' | 'deleteCols';
|
|
56
|
+
/** 0-based index the edit happens at. */
|
|
57
|
+
at: number;
|
|
58
|
+
count: number;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Rewrite a formula's references after rows or columns are inserted or
|
|
62
|
+
* removed. A reference into the deleted span becomes `#REF!`.
|
|
63
|
+
*
|
|
64
|
+
* A RANGE only breaks when the edit removes all of it. Deleting rows inside a
|
|
65
|
+
* range shrinks it, which is what Excel does and what keeps `=SUM(A1:A10)`
|
|
66
|
+
* meaningful after you delete row 5.
|
|
67
|
+
*/
|
|
68
|
+
/**
|
|
69
|
+
* Which sheet an edit happened on, and which sheet the formula being
|
|
70
|
+
* rewritten lives on. With a scope, only references INTO the edited sheet
|
|
71
|
+
* move: an unqualified reference belongs to `self`, a qualified one to the
|
|
72
|
+
* sheet it names. Without one every reference moves, which is right for a
|
|
73
|
+
* single grid that has no sheets to tell apart.
|
|
74
|
+
*/
|
|
75
|
+
export type EditScope = {
|
|
76
|
+
/** The sheet the rows or columns were inserted in or deleted from. */
|
|
77
|
+
sheet: string;
|
|
78
|
+
/** The sheet the formula lives on; null for a defined name, which has no
|
|
79
|
+
* home sheet, so only its qualified references can match. */
|
|
80
|
+
self: string | null;
|
|
81
|
+
};
|
|
82
|
+
export declare function fixupReferences(text: unknown, edit: StructuralEdit, scope?: EditScope): unknown;
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reference rewriting. The one module allowed to move a reference.
|
|
3
|
+
*
|
|
4
|
+
* Two jobs, both of which the demo engines could not do because they discarded
|
|
5
|
+
* `$` at parse time:
|
|
6
|
+
*
|
|
7
|
+
* - `translateFormula` shifts RELATIVE parts by a delta and leaves pinned
|
|
8
|
+
* parts alone. This is what makes fill-down, copy/paste and drag-fill
|
|
9
|
+
* correct: `=$A$1*B2` filled one row down becomes `=$A$1*B3`.
|
|
10
|
+
* - `fixupReferences` rewrites every formula in a sheet after rows or columns
|
|
11
|
+
* are inserted or removed. A reference to a deleted cell becomes `#REF!`;
|
|
12
|
+
* everything past the edit shifts; a range straddling an insertion grows.
|
|
13
|
+
*
|
|
14
|
+
* Both re-serialise from the AST rather than editing text, so `SUM( A1 : B2 )`
|
|
15
|
+
* comes back normalised instead of half-rewritten.
|
|
16
|
+
*/
|
|
17
|
+
import { mapNode, PRECEDENCE } from './ast.js';
|
|
18
|
+
import { formatA1, colToLetters, lettersToCol } from './address.js';
|
|
19
|
+
import { parseFormula } from './parse.js';
|
|
20
|
+
/** Shift a single reference's relative parts. */
|
|
21
|
+
function translateRef(ref, dRow, dCol) {
|
|
22
|
+
return {
|
|
23
|
+
...ref,
|
|
24
|
+
col: ref.colAbs ? ref.col : ref.col + dCol,
|
|
25
|
+
row: ref.row === null || ref.rowAbs ? ref.row : ref.row + dRow,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** True once a reference has been shifted off the sheet. */
|
|
29
|
+
function isBroken(ref) {
|
|
30
|
+
return ref.col < 0 || (ref.row !== null && ref.row < 0);
|
|
31
|
+
}
|
|
32
|
+
/** Serialise an AST back to formula text, with the leading `=`. */
|
|
33
|
+
export function formatFormula(node) {
|
|
34
|
+
return `=${render(node)}`;
|
|
35
|
+
}
|
|
36
|
+
/** Render `node` as an operand of something binding at `minPrec`, adding
|
|
37
|
+
* parentheses when it would otherwise re-associate. */
|
|
38
|
+
function wrap(node, minPrec) {
|
|
39
|
+
const text = render(node);
|
|
40
|
+
if (node.k === 'binary' && PRECEDENCE[node.op] < minPrec)
|
|
41
|
+
return `(${text})`;
|
|
42
|
+
// A unary minus has to be parenthesised inside anything tighter than it,
|
|
43
|
+
// so -(A1+B1) and 2^-A1 both survive.
|
|
44
|
+
if (node.k === 'unary' && node.op !== '%' && minPrec > 5)
|
|
45
|
+
return `(${text})`;
|
|
46
|
+
return text;
|
|
47
|
+
}
|
|
48
|
+
function render(node) {
|
|
49
|
+
switch (node.k) {
|
|
50
|
+
case 'num': return String(node.v);
|
|
51
|
+
case 'str': return `"${node.v.replace(/"/g, '""')}"`;
|
|
52
|
+
case 'bool': return node.v ? 'TRUE' : 'FALSE';
|
|
53
|
+
case 'ref': return node.ref.col < 0 || (node.ref.row ?? 0) < 0 ? '#REF!' : formatA1(node.ref);
|
|
54
|
+
case 'range': {
|
|
55
|
+
if (isBroken(node.from) || isBroken(node.to))
|
|
56
|
+
return '#REF!';
|
|
57
|
+
// A whole-column range prints as A:C, not A1:C.
|
|
58
|
+
if (node.from.row === null || node.to.row === null) {
|
|
59
|
+
const left = `${node.from.colAbs ? '$' : ''}${colToLetters(node.from.col)}`;
|
|
60
|
+
const right = `${node.to.colAbs ? '$' : ''}${colToLetters(node.to.col)}`;
|
|
61
|
+
const prefix = node.from.sheet ? `${formatA1({ ...node.from, row: null }).split('!')[0]}!` : '';
|
|
62
|
+
return `${prefix}${left}:${right}`;
|
|
63
|
+
}
|
|
64
|
+
const from = formatA1(node.from);
|
|
65
|
+
// The right-hand side drops a repeated sheet prefix: Sheet1!A1:B2.
|
|
66
|
+
const to = formatA1({ ...node.to, sheet: null });
|
|
67
|
+
return `${from}:${to}`;
|
|
68
|
+
}
|
|
69
|
+
case 'name': return node.name;
|
|
70
|
+
case 'table': {
|
|
71
|
+
// Re-serialised, never translated: the whole point of a structured
|
|
72
|
+
// reference is that it does not move when the formula does.
|
|
73
|
+
const parts = [];
|
|
74
|
+
if (node.specifier === '#ThisRow')
|
|
75
|
+
parts.push(node.column ? `@${node.column}` : '@');
|
|
76
|
+
else if (node.specifier !== '#Data')
|
|
77
|
+
parts.push(node.specifier);
|
|
78
|
+
if (node.specifier !== '#ThisRow' && node.column) {
|
|
79
|
+
parts.push(node.columnTo ? `[${node.column}]:[${node.columnTo}]` : node.column);
|
|
80
|
+
}
|
|
81
|
+
return `${node.table ?? ''}[${parts.join(',')}]`;
|
|
82
|
+
}
|
|
83
|
+
case 'unary':
|
|
84
|
+
return node.op === '%'
|
|
85
|
+
? `${wrap(node.arg, 6)}%`
|
|
86
|
+
: `${node.op}${wrap(node.arg, 5)}`;
|
|
87
|
+
case 'binary': {
|
|
88
|
+
const prec = PRECEDENCE[node.op];
|
|
89
|
+
// The AST does not record parentheses, so they have to be reconstructed
|
|
90
|
+
// from precedence. Emitting the operands bare turns =(A1+B1)*2 into
|
|
91
|
+
// =A2+B2*2, which is a silent wrong answer on every fill and paste.
|
|
92
|
+
//
|
|
93
|
+
// Which SIDE needs one at equal precedence depends on associativity.
|
|
94
|
+
// Left-associative: the right operand does, because a-(b-c) is not
|
|
95
|
+
// a-b-c. Right-associative `^` is the mirror: the LEFT operand does,
|
|
96
|
+
// because (a^b)^c is not a^b^c.
|
|
97
|
+
const rightAssociative = node.op === '^';
|
|
98
|
+
const leftMin = rightAssociative ? prec + 1 : prec;
|
|
99
|
+
const rightMin = rightAssociative ? prec : prec + 1;
|
|
100
|
+
return `${wrap(node.left, leftMin)}${node.op}${wrap(node.right, rightMin)}`;
|
|
101
|
+
}
|
|
102
|
+
case 'fn': return `${node.name}(${node.args.map(render).join(',')})`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Shift every relative reference in a formula by (dRow, dCol).
|
|
107
|
+
*
|
|
108
|
+
* Anything that is not a formula comes back untouched, so callers can pass a
|
|
109
|
+
* whole column of mixed literals and formulas through it.
|
|
110
|
+
*/
|
|
111
|
+
export function translateFormula(text, dRow, dCol) {
|
|
112
|
+
if (typeof text !== 'string' || !text.startsWith('='))
|
|
113
|
+
return text;
|
|
114
|
+
if (dRow === 0 && dCol === 0)
|
|
115
|
+
return text;
|
|
116
|
+
let ast;
|
|
117
|
+
try {
|
|
118
|
+
ast = parseFormula(text);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// Not parseable: leave it exactly as the user typed it. Rewriting a
|
|
122
|
+
// half-understood formula is worse than leaving it alone.
|
|
123
|
+
return text;
|
|
124
|
+
}
|
|
125
|
+
const moved = mapNode(ast, (n) => {
|
|
126
|
+
if (n.k === 'ref')
|
|
127
|
+
return { ...n, ref: translateRef(n.ref, dRow, dCol) };
|
|
128
|
+
if (n.k === 'range') {
|
|
129
|
+
return { ...n, from: translateRef(n.from, dRow, dCol), to: translateRef(n.to, dRow, dCol) };
|
|
130
|
+
}
|
|
131
|
+
return n;
|
|
132
|
+
});
|
|
133
|
+
return formatFormula(moved);
|
|
134
|
+
}
|
|
135
|
+
export const REFERENCE_COLOURS = ['#1e6fd9', '#d62828', '#8e44ad', '#0f9d58', '#e67e22', '#0097a7'];
|
|
136
|
+
export function referenceSpans(text, colours = REFERENCE_COLOURS) {
|
|
137
|
+
const out = [];
|
|
138
|
+
if (!text.startsWith('='))
|
|
139
|
+
return out;
|
|
140
|
+
const blanked = text
|
|
141
|
+
.replace(/"[^"]*"/g, (m) => ' '.repeat(m.length))
|
|
142
|
+
// A sheet-qualified reference belongs to another sheet.
|
|
143
|
+
.replace(/(?:'[^']*'|[A-Za-z_][\w.]*)!\$?[A-Z]{1,3}\$?\d+(?::\$?[A-Z]{1,3}\$?\d+)?/gi, (m) => ' '.repeat(m.length));
|
|
144
|
+
// Case-insensitive: a reference is typed as "b5" as often as "B5", and
|
|
145
|
+
// the engine reads both.
|
|
146
|
+
const re = /(?<![A-Za-z0-9_.!])\$?([A-Za-z]{1,3})\$?(\d{1,7})(?::\$?([A-Za-z]{1,3})\$?(\d{1,7}))?(?![A-Za-z0-9_(])/g;
|
|
147
|
+
const order = new Map();
|
|
148
|
+
for (const m of blanked.matchAll(re)) {
|
|
149
|
+
const key = m[0].replace(/\$/g, '').toUpperCase();
|
|
150
|
+
const c1 = lettersToCol(m[1].toUpperCase()), r1 = Number(m[2]) - 1;
|
|
151
|
+
const c2 = m[3] ? lettersToCol(m[3].toUpperCase()) : c1, r2 = m[4] ? Number(m[4]) - 1 : r1;
|
|
152
|
+
if (c1 < 0 || c2 < 0 || r1 < 0 || r2 < 0)
|
|
153
|
+
continue;
|
|
154
|
+
let index = order.get(key);
|
|
155
|
+
if (index === undefined) {
|
|
156
|
+
index = order.size;
|
|
157
|
+
order.set(key, index);
|
|
158
|
+
}
|
|
159
|
+
out.push({
|
|
160
|
+
start: m.index,
|
|
161
|
+
end: m.index + m[0].length,
|
|
162
|
+
key,
|
|
163
|
+
colour: colours[index % colours.length],
|
|
164
|
+
rect: [Math.min(r1, r2), Math.min(c1, c2), Math.max(r1, r2), Math.max(c1, c2)],
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
export function renameSheetReferences(text, from, to) {
|
|
170
|
+
if (typeof text !== 'string' || !text.startsWith('='))
|
|
171
|
+
return text;
|
|
172
|
+
const wanted = from.toLowerCase();
|
|
173
|
+
if (!text.toLowerCase().includes(wanted))
|
|
174
|
+
return text;
|
|
175
|
+
let ast;
|
|
176
|
+
try {
|
|
177
|
+
ast = parseFormula(text);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return text;
|
|
181
|
+
}
|
|
182
|
+
let changed = false;
|
|
183
|
+
const swap = (ref) => {
|
|
184
|
+
if (ref.sheet === null || ref.sheet.toLowerCase() !== wanted)
|
|
185
|
+
return ref;
|
|
186
|
+
changed = true;
|
|
187
|
+
return { ...ref, sheet: to };
|
|
188
|
+
};
|
|
189
|
+
const moved = mapNode(ast, (n) => {
|
|
190
|
+
if (n.k === 'ref')
|
|
191
|
+
return { ...n, ref: swap(n.ref) };
|
|
192
|
+
if (n.k === 'range')
|
|
193
|
+
return { ...n, from: swap(n.from), to: swap(n.to) };
|
|
194
|
+
return n;
|
|
195
|
+
});
|
|
196
|
+
return changed ? formatFormula(moved) : text;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Shift one coordinate through a structural edit. Returns null when the thing
|
|
200
|
+
* it pointed at was deleted, which becomes `#REF!`.
|
|
201
|
+
*/
|
|
202
|
+
function shiftCoord(value, edit, inserting) {
|
|
203
|
+
if (inserting) {
|
|
204
|
+
return value >= edit.at ? value + edit.count : value;
|
|
205
|
+
}
|
|
206
|
+
if (value < edit.at)
|
|
207
|
+
return value;
|
|
208
|
+
if (value < edit.at + edit.count)
|
|
209
|
+
return null;
|
|
210
|
+
return value - edit.count;
|
|
211
|
+
}
|
|
212
|
+
function fixRef(ref, edit) {
|
|
213
|
+
const rows = edit.kind === 'insertRows' || edit.kind === 'deleteRows';
|
|
214
|
+
const inserting = edit.kind === 'insertRows' || edit.kind === 'insertCols';
|
|
215
|
+
if (rows) {
|
|
216
|
+
if (ref.row === null)
|
|
217
|
+
return ref;
|
|
218
|
+
const next = shiftCoord(ref.row, edit, inserting);
|
|
219
|
+
return next === null ? null : { ...ref, row: next };
|
|
220
|
+
}
|
|
221
|
+
const next = shiftCoord(ref.col, edit, inserting);
|
|
222
|
+
return next === null ? null : { ...ref, col: next };
|
|
223
|
+
}
|
|
224
|
+
const sameSheet = (a, b) => a !== null && a.toLowerCase() === b.toLowerCase();
|
|
225
|
+
export function fixupReferences(text, edit, scope) {
|
|
226
|
+
if (typeof text !== 'string' || !text.startsWith('='))
|
|
227
|
+
return text;
|
|
228
|
+
if (edit.count <= 0)
|
|
229
|
+
return text;
|
|
230
|
+
let ast;
|
|
231
|
+
try {
|
|
232
|
+
ast = parseFormula(text);
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return text;
|
|
236
|
+
}
|
|
237
|
+
const broken = { col: -1, colAbs: false, row: -1, rowAbs: false, sheet: null };
|
|
238
|
+
const touched = (sheet) => !scope || sameSheet(sheet ?? scope.self, scope.sheet);
|
|
239
|
+
const moved = mapNode(ast, (n) => {
|
|
240
|
+
if (n.k === 'ref') {
|
|
241
|
+
if (!touched(n.ref.sheet))
|
|
242
|
+
return n;
|
|
243
|
+
const next = fixRef(n.ref, edit);
|
|
244
|
+
return next === null ? { k: 'ref', ref: broken } : { ...n, ref: next };
|
|
245
|
+
}
|
|
246
|
+
if (n.k === 'range') {
|
|
247
|
+
if (!touched(n.from.sheet ?? n.to.sheet))
|
|
248
|
+
return n;
|
|
249
|
+
const from = fixRef(n.from, edit);
|
|
250
|
+
const to = fixRef(n.to, edit);
|
|
251
|
+
// Both ends gone means the whole range was deleted.
|
|
252
|
+
if (from === null && to === null)
|
|
253
|
+
return { k: 'ref', ref: broken };
|
|
254
|
+
// One end gone: clamp it to the edit point so the range shrinks.
|
|
255
|
+
const rows = edit.kind === 'deleteRows';
|
|
256
|
+
const clamp = (ref) => rows ? { ...ref, row: Math.max(edit.at - 1, 0) } : { ...ref, col: Math.max(edit.at - 1, 0) };
|
|
257
|
+
return {
|
|
258
|
+
...n,
|
|
259
|
+
from: from ?? clamp(n.from),
|
|
260
|
+
to: to ?? clamp(n.to),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
return n;
|
|
264
|
+
});
|
|
265
|
+
return formatFormula(moved);
|
|
266
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ribbon's icons, as path data on a 16 x 16 grid.
|
|
3
|
+
*
|
|
4
|
+
* Drawn here rather than pulled from an icon font or a package: the ribbon
|
|
5
|
+
* is the one place in the library that needs a coordinated set of command
|
|
6
|
+
* glyphs (a clipboard, scissors, a paint bucket, a funnel), and an icon set
|
|
7
|
+
* that is part of the source can be read, tested and tuned like anything
|
|
8
|
+
* else. `SvRibbonIcon.svelte` renders one by name; a large button draws the
|
|
9
|
+
* same paths at twice the size, which is why they are vectors.
|
|
10
|
+
*
|
|
11
|
+
* One style, one colour. These are line icons - a 1.4px stroke with round
|
|
12
|
+
* caps and joins, the way Excel's own monochrome set is drawn - and every
|
|
13
|
+
* one of them is `currentColor`, so they are black on a light ribbon and
|
|
14
|
+
* white on a dark one and never carry a colour of their own. The only fills
|
|
15
|
+
* are the small solid parts Excel also fills: a dot, a corner, the head of
|
|
16
|
+
* an arrow. Colour belongs to the colour bar under Fill Colour and Font
|
|
17
|
+
* Colour, which shows the colour the face will apply, not to the glyph.
|
|
18
|
+
*/
|
|
19
|
+
export type IconPath = {
|
|
20
|
+
d: string;
|
|
21
|
+
/** Solid shape in the icon colour instead of a stroked outline. */
|
|
22
|
+
fill?: boolean;
|
|
23
|
+
/** Stroke width when not the default 1.4. */
|
|
24
|
+
width?: number;
|
|
25
|
+
/** Dashed stroke, for the "no border" sides. */
|
|
26
|
+
dash?: string;
|
|
27
|
+
};
|
|
28
|
+
export type RibbonIconName = 'undo' | 'redo' | 'paste' | 'cut' | 'copy' | 'paste-special' | 'font-grow' | 'font-shrink' | 'borders' | 'border-none' | 'border-bottom' | 'border-top' | 'border-left' | 'border-right' | 'border-all' | 'border-outside' | 'border-thick-bottom' | 'fill-colour' | 'font-colour' | 'align-left' | 'align-center' | 'align-right' | 'wrap' | 'dec-more' | 'dec-less' | 'insert-cells' | 'delete-cells' | 'freeze' | 'unfreeze' | 'autosum' | 'fill-down' | 'fill-right' | 'clear' | 'find' | 'filter' | 'sort-asc' | 'sort-desc' | 'chart' | 'table' | 'function' | 'new-sheet' | 'name-manager' | 'show-formulas' | 'calculate' | 'goal-seek' | 'text-to-columns' | 'remove-duplicates' | 'chevron-down' | 'format-cells' | 'format-painter' | 'protect' | 'unprotect' | 'lock' | 'comment' | 'comment-delete' | 'comment-prev' | 'comment-next' | 'comments-all' | 'validation' | 'cf' | 'cf-greater' | 'cf-less' | 'cf-between' | 'cf-equal' | 'cf-text' | 'cf-duplicates' | 'cf-top' | 'cf-bottom' | 'cf-above' | 'cf-below' | 'cf-bar' | 'cf-scale' | 'cf-icons' | 'merge' | 'unmerge' | 'gridlines' | 'formula-bar' | 'headings' | 'font' | 'number' | 'clear-formats' | 'column-width' | 'row-height' | 'hide' | 'unhide';
|
|
29
|
+
export declare const RIBBON_ICONS: Record<RibbonIconName, ReadonlyArray<IconPath>>;
|
|
30
|
+
/** The names, for tests and for anyone building their own toolbar. */
|
|
31
|
+
export declare const RIBBON_ICON_NAMES: RibbonIconName[];
|