@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.
Files changed (197) hide show
  1. package/dist/SvFormulaBar.svelte +606 -0
  2. package/dist/SvFormulaBar.svelte.d.ts +62 -0
  3. package/dist/SvPivotDesigner.svelte +65 -14
  4. package/dist/SvRibbonIcon.svelte +46 -0
  5. package/dist/SvRibbonIcon.svelte.d.ts +17 -0
  6. package/dist/SvSchemaChart.svelte +4 -2
  7. package/dist/SvSheet.svelte +4114 -0
  8. package/dist/SvSheet.svelte.d.ts +142 -0
  9. package/dist/SvSheetComment.svelte +98 -0
  10. package/dist/SvSheetComment.svelte.d.ts +22 -0
  11. package/dist/SvSheetConditionalFormat.svelte +162 -0
  12. package/dist/SvSheetConditionalFormat.svelte.d.ts +14 -0
  13. package/dist/SvSheetDataValidation.svelte +249 -0
  14. package/dist/SvSheetDataValidation.svelte.d.ts +14 -0
  15. package/dist/SvSheetFilterMenu.svelte +217 -0
  16. package/dist/SvSheetFilterMenu.svelte.d.ts +17 -0
  17. package/dist/SvSheetFindReplace.svelte +132 -0
  18. package/dist/SvSheetFindReplace.svelte.d.ts +10 -0
  19. package/dist/SvSheetFormatCells.svelte +421 -0
  20. package/dist/SvSheetFormatCells.svelte.d.ts +17 -0
  21. package/dist/SvSheetGoalSeek.svelte +144 -0
  22. package/dist/SvSheetGoalSeek.svelte.d.ts +12 -0
  23. package/dist/SvSheetInsertFunction.svelte +147 -0
  24. package/dist/SvSheetInsertFunction.svelte.d.ts +8 -0
  25. package/dist/SvSheetListPicker.svelte +89 -0
  26. package/dist/SvSheetListPicker.svelte.d.ts +16 -0
  27. package/dist/SvSheetManageRules.svelte +185 -0
  28. package/dist/SvSheetManageRules.svelte.d.ts +16 -0
  29. package/dist/SvSheetNameManager.svelte +225 -0
  30. package/dist/SvSheetNameManager.svelte.d.ts +11 -0
  31. package/dist/SvSheetPasteSpecial.svelte +105 -0
  32. package/dist/SvSheetPasteSpecial.svelte.d.ts +12 -0
  33. package/dist/SvSheetRemoveDuplicates.svelte +149 -0
  34. package/dist/SvSheetRemoveDuplicates.svelte.d.ts +12 -0
  35. package/dist/SvSheetRibbon.svelte +1201 -0
  36. package/dist/SvSheetRibbon.svelte.d.ts +34 -0
  37. package/dist/SvSheetSizeDialog.svelte +58 -0
  38. package/dist/SvSheetSizeDialog.svelte.d.ts +11 -0
  39. package/dist/SvSheetTabs.svelte +441 -0
  40. package/dist/SvSheetTabs.svelte.d.ts +36 -0
  41. package/dist/SvSheetTabs.test-harness.svelte.d.ts +17 -0
  42. package/dist/SvSheetTabs.test-harness.svelte.js +22 -0
  43. package/dist/SvSheetTextToColumns.svelte +172 -0
  44. package/dist/SvSheetTextToColumns.svelte.d.ts +13 -0
  45. package/dist/SvSheetValidationAlert.svelte +83 -0
  46. package/dist/SvSheetValidationAlert.svelte.d.ts +15 -0
  47. package/dist/cdn/svgrid-enterprise.svelte-external.js +18997 -5396
  48. package/dist/designer/assets/GridMenus-BlsvL_aU.js +8 -0
  49. package/dist/designer/assets/SvChartMenu-BYO9cYdy.css +1 -0
  50. package/dist/designer/assets/SvChartMenu-D9hEeHar.js +1 -0
  51. package/dist/designer/assets/SvDateRangeInput-BBRzrQeg.js +1 -0
  52. package/dist/designer/assets/SvDateTimePicker-DARvKUL2.js +1 -0
  53. package/dist/designer/assets/SvGridCellEditor-C7uQXYds.js +4 -0
  54. package/dist/designer/assets/SvGridChart-B8j1SSy3.js +3 -0
  55. package/dist/designer/assets/SvGridChart-BgLuPO7I.css +1 -0
  56. package/dist/designer/assets/SvGridChartBuilder-08YWWeqY.css +1 -0
  57. package/dist/designer/assets/SvGridChartBuilder-kvyIU_Rl.js +1 -0
  58. package/dist/designer/assets/SvGridChartPanel-4Pc8y5yO.css +1 -0
  59. package/dist/designer/assets/SvGridChartPanel-pT6ITc2P.js +4 -0
  60. package/dist/designer/assets/{SvGridChartView-DUc-1RUf.css → SvGridChartView-7msfnWZT.css} +1 -1
  61. package/dist/designer/assets/SvGridChartView-bg281UA1.js +1 -0
  62. package/dist/designer/assets/SvListBox-BDS66lmZ.js +110 -0
  63. package/dist/designer/assets/SvMenuList-DJ47ba1h.css +1 -0
  64. package/dist/designer/assets/SvMenuList-Dgq2D5VN.js +1 -0
  65. package/dist/designer/assets/SvTabs-DKmwb5p1.css +1 -0
  66. package/dist/designer/assets/SvTabs-UFCMTHXS.js +1 -0
  67. package/dist/designer/assets/chart-cIPUsbM5.js +1 -0
  68. package/dist/designer/assets/chart-export-B6bkQ3X8.js +11 -0
  69. package/dist/designer/assets/chart-export-pdf-BbA28-gt.js +11 -0
  70. package/dist/designer/assets/chart-panel-messages-DtTxpBUU.js +1 -0
  71. package/dist/designer/assets/chart-summary-Czu_5epb.js +1 -0
  72. package/dist/designer/assets/chart-trend-D5I5VEe8.js +1 -0
  73. package/dist/designer/assets/chart-validate-BMqM-qkF.js +1 -0
  74. package/dist/designer/assets/column-resize-BYDzR1Ve.js +1 -0
  75. package/dist/designer/assets/date-format-B8JbEtIG.js +1 -0
  76. package/dist/designer/assets/date-format-DKS-z0xS.css +1 -0
  77. package/dist/designer/assets/editor-contract-Bpoa7A5g.js +1 -0
  78. package/dist/designer/assets/focus-trap-CAreAhlU.js +1 -0
  79. package/dist/designer/assets/index-CMFgAoVV.css +1 -0
  80. package/dist/designer/assets/{index-C_a9Cj8Y.js → index-DwU7CAMm.js} +291 -290
  81. package/dist/designer/assets/list-option-BP_dWS66.js +1 -0
  82. package/dist/designer/assets/row-resize-DLktcqUK.js +1 -0
  83. package/dist/designer/assets/src-CITpcm6b.js +1 -0
  84. package/dist/designer/assets/src-EdI3Xqds.js +58 -0
  85. package/dist/designer/index.html +23 -14
  86. package/dist/export-pdf.d.ts +89 -0
  87. package/dist/export-pdf.js +130 -0
  88. package/dist/export.js +17 -10
  89. package/dist/index.d.ts +64 -1
  90. package/dist/index.js +68 -1
  91. package/dist/install.js +5 -0
  92. package/dist/node/studio.js +64 -2
  93. package/dist/pivot-chart.d.ts +25 -1
  94. package/dist/pivot-chart.js +25 -69
  95. package/dist/sheet/address.d.ts +43 -0
  96. package/dist/sheet/address.js +81 -0
  97. package/dist/sheet/ast.d.ts +94 -0
  98. package/dist/sheet/ast.js +56 -0
  99. package/dist/sheet/auto-filter.d.ts +66 -0
  100. package/dist/sheet/auto-filter.js +147 -0
  101. package/dist/sheet/autocomplete.d.ts +42 -0
  102. package/dist/sheet/autocomplete.js +162 -0
  103. package/dist/sheet/coerce.d.ts +31 -0
  104. package/dist/sheet/coerce.js +122 -0
  105. package/dist/sheet/commands.d.ts +89 -0
  106. package/dist/sheet/commands.js +230 -0
  107. package/dist/sheet/comments.d.ts +29 -0
  108. package/dist/sheet/comments.js +73 -0
  109. package/dist/sheet/conditional-formats.d.ts +144 -0
  110. package/dist/sheet/conditional-formats.js +316 -0
  111. package/dist/sheet/deps.d.ts +60 -0
  112. package/dist/sheet/deps.js +199 -0
  113. package/dist/sheet/document.d.ts +150 -0
  114. package/dist/sheet/document.js +264 -0
  115. package/dist/sheet/edit-keys.d.ts +14 -0
  116. package/dist/sheet/edit-keys.js +40 -0
  117. package/dist/sheet/entry.d.ts +31 -0
  118. package/dist/sheet/entry.js +76 -0
  119. package/dist/sheet/evaluate.d.ts +61 -0
  120. package/dist/sheet/evaluate.js +337 -0
  121. package/dist/sheet/find-replace.d.ts +62 -0
  122. package/dist/sheet/find-replace.js +152 -0
  123. package/dist/sheet/format-store.d.ts +99 -0
  124. package/dist/sheet/format-store.js +233 -0
  125. package/dist/sheet/freeze.d.ts +54 -0
  126. package/dist/sheet/freeze.js +58 -0
  127. package/dist/sheet/function-catalog.d.ts +10 -0
  128. package/dist/sheet/function-catalog.js +94 -0
  129. package/dist/sheet/functions.d.ts +28 -0
  130. package/dist/sheet/functions.js +362 -0
  131. package/dist/sheet/goal-seek.d.ts +73 -0
  132. package/dist/sheet/goal-seek.js +153 -0
  133. package/dist/sheet/index.d.ts +53 -0
  134. package/dist/sheet/index.js +53 -0
  135. package/dist/sheet/merges.d.ts +71 -0
  136. package/dist/sheet/merges.js +137 -0
  137. package/dist/sheet/names.d.ts +31 -0
  138. package/dist/sheet/names.js +87 -0
  139. package/dist/sheet/navigate.d.ts +52 -0
  140. package/dist/sheet/navigate.js +135 -0
  141. package/dist/sheet/number-format.d.ts +63 -0
  142. package/dist/sheet/number-format.js +508 -0
  143. package/dist/sheet/palette.d.ts +19 -0
  144. package/dist/sheet/palette.js +84 -0
  145. package/dist/sheet/parse.d.ts +13 -0
  146. package/dist/sheet/parse.js +108 -0
  147. package/dist/sheet/paste-special.d.ts +164 -0
  148. package/dist/sheet/paste-special.js +592 -0
  149. package/dist/sheet/protection.d.ts +21 -0
  150. package/dist/sheet/protection.js +48 -0
  151. package/dist/sheet/rects.d.ts +45 -0
  152. package/dist/sheet/rects.js +112 -0
  153. package/dist/sheet/refs.d.ts +82 -0
  154. package/dist/sheet/refs.js +266 -0
  155. package/dist/sheet/ribbon-icons.d.ts +31 -0
  156. package/dist/sheet/ribbon-icons.js +350 -0
  157. package/dist/sheet/ribbon.d.ts +173 -0
  158. package/dist/sheet/ribbon.js +753 -0
  159. package/dist/sheet/shortcuts.d.ts +164 -0
  160. package/dist/sheet/shortcuts.js +623 -0
  161. package/dist/sheet/structure.d.ts +87 -0
  162. package/dist/sheet/structure.js +166 -0
  163. package/dist/sheet/tables.d.ts +70 -0
  164. package/dist/sheet/tables.js +147 -0
  165. package/dist/sheet/tokenize.d.ts +54 -0
  166. package/dist/sheet/tokenize.js +304 -0
  167. package/dist/sheet/transforms.d.ts +76 -0
  168. package/dist/sheet/transforms.js +154 -0
  169. package/dist/sheet/validation.d.ts +92 -0
  170. package/dist/sheet/validation.js +241 -0
  171. package/dist/sheet/workbook.d.ts +72 -0
  172. package/dist/sheet/workbook.js +409 -0
  173. package/dist/sheet-enable.d.ts +6 -0
  174. package/dist/sheet-enable.js +46 -0
  175. package/dist/studio/ui-components.generated.js +63 -1
  176. package/dist/version.d.ts +1 -1
  177. package/dist/version.js +1 -1
  178. package/package.json +9 -4
  179. package/dist/designer/assets/GridMenus-B1JtIN21.js +0 -8
  180. package/dist/designer/assets/SvDateRangeInput-CjO7nn_x.js +0 -1
  181. package/dist/designer/assets/SvDateTimePicker-ByBD_ov6.js +0 -1
  182. package/dist/designer/assets/SvField-YDLipR6O.css +0 -1
  183. package/dist/designer/assets/SvField-cj1PFNgZ.js +0 -1
  184. package/dist/designer/assets/SvGridCellEditor-0W1L2hJ1.js +0 -2
  185. package/dist/designer/assets/SvGridChart-DBOR4xcz.js +0 -2
  186. package/dist/designer/assets/SvGridChart-fkMWeh1J.css +0 -1
  187. package/dist/designer/assets/SvGridChartPanel-symrhNnR.js +0 -11
  188. package/dist/designer/assets/SvGridChartView-BmRxq6mU.js +0 -1
  189. package/dist/designer/assets/SvListBox-1oUF6Iav.js +0 -110
  190. package/dist/designer/assets/chart-DXFganU8.js +0 -1
  191. package/dist/designer/assets/column-resize-C_inr5zT.js +0 -1
  192. package/dist/designer/assets/date-format-DGagAqRc.css +0 -1
  193. package/dist/designer/assets/date-format-onlYDsrY.js +0 -1
  194. package/dist/designer/assets/easing-zaypKrr0.js +0 -3
  195. package/dist/designer/assets/index-k2stW6L8.css +0 -1
  196. package/dist/designer/assets/row-resize-B0FWHcNr.js +0 -1
  197. package/dist/designer/assets/src-D9p27OGC.js +0 -58
@@ -0,0 +1,53 @@
1
+ /**
2
+ * `@svgrid/enterprise/sheet` - the Excel keyboard layer on its own subpath.
3
+ *
4
+ * Importing the package barrel pulls every module it reaches into the chunk
5
+ * (the package sets no `sideEffects` flag), which for an app that only wants
6
+ * the shortcuts would mean shipping export, pivot, the scheduler and the board
7
+ * as well. This subpath is the shortcuts and nothing else. It matters more once
8
+ * the formula engine lands beside them in phase 2.
9
+ */
10
+ export { enableSheet } from '../sheet-enable.js';
11
+ export { createTableRegistry, isValidTableName, resolveTableRange, columnIndexOf, rowCountOf, } from './tables.js';
12
+ export { goalSeek, goalSeekCell, } from './goal-seek.js';
13
+ export { default as SvSheetTabs } from '../SvSheetTabs.svelte';
14
+ export { createWorkbook, isValidSheetName, } from './workbook.js';
15
+ export { setWorkbook, getWorkbook } from './shortcuts.js';
16
+ export { createSheetDocument, } from './document.js';
17
+ export { shiftRect, shiftRects, subtractRect, rectContains, rectsIntersect, remapNotes, lineShift } from './rects.js';
18
+ export { isLocked, rectsHaveLocked, rectsMixLocked, PROTECTED_MESSAGE } from './protection.js';
19
+ export { commentAt, withComment, listComments, nextComment } from './comments.js';
20
+ export { ruleAt, rulesIn, checkEntry, listChoices, shiftValidation, removeValidation, describeRule, dateValue, validationId, DEFAULT_ALERT_MESSAGE, OPERATOR_LABELS, } from './validation.js';
21
+ export { ruleStats, evaluateCf, shiftCf, removeCf, cfIn, describeCf, scaleColor, iconIndex, hasStyle, cfId, CF_PRESET_STYLES, COLOR_SCALES, DATA_BAR_COLOR, } from './conditional-formats.js';
22
+ export { mergePlan, unmergePlan, mergeDropsValues, mergesIn, mergeAt as sheetMergeAt, isCoveredCell, selectionMerged, toGridMerges, sortBlockedByMerges, reorderMerges, normalRect, } from './merges.js';
23
+ export { distinctValues, hiddenRowsFor, passesFilter, isFiltering, withColumnFilter, valuesFilter, shiftAutoFilter, describeFilter, } from './auto-filter.js';
24
+ export { splitText, textToColumns, guessDelimiter, findDuplicates, removeDuplicates, } from './transforms.js';
25
+ export { insertRows, deleteRows, insertColumns, deleteColumns, axisForSelection, setStructureTarget, getStructureTarget, rewriteFormulas, } from './structure.js';
26
+ export { findAll, findNext, replaceOne, replaceAll, replaceInText, cellMatches, setFindTarget, getFindTarget, } from './find-replace.js';
27
+ export { splitFrozenRows, frozenColumnIds, applyFreeze, freezeAtActiveCell, freezeTopRow, freezeFirstColumn, unfreeze, } from './freeze.js';
28
+ export { buildClipboardPayload, parseClipboard, parseClipboardText, parseClipboardHtml, readClipboardOrigin, anchorForeignFormulas, resolvePasteCell, planPaste, msoNumberFormat, numFmtFromMso, isR1C1, r1c1ToA1, } from './paste-special.js';
29
+ export { setFindReplaceHandler, setPasteSpecialHandler } from './shortcuts.js';
30
+ export { default as SvFormulaBar } from '../SvFormulaBar.svelte';
31
+ export { default as SvSheet } from '../SvSheet.svelte';
32
+ export { default as SvSheetRibbon } from '../SvSheetRibbon.svelte';
33
+ export { RIBBON_TABS, ribbonItems, withDecimals, applyBorders, } from './ribbon.js';
34
+ export { RIBBON_ICONS, RIBBON_ICON_NAMES } from './ribbon-icons.js';
35
+ export { THEME_COLOURS, STANDARD_COLOURS, ALL_COLOURS, tint } from './palette.js';
36
+ export { default as SvRibbonIcon } from '../SvRibbonIcon.svelte';
37
+ export { move, selectRegion, selectLine, applyFormat, toggleFormat, preset, autoSum, structural, switchSheet, gridOf, } from './shortcuts.js';
38
+ export { compileNumberFormat, formatWithPattern, FORMAT_PRESETS, } from './number-format.js';
39
+ export { createFormatStore, entryToStyle, } from './format-store.js';
40
+ export { createNames, isValidName, } from './names.js';
41
+ export { suggestFunctions, applySuggestion, signatureAt, partialAt, SIGNATURES, } from './autocomplete.js';
42
+ export { setFormatTarget, getFormatTarget, setFormatDialogHandler, } from './shortcuts.js';
43
+ export { parseFormula, parse, } from './parse.js';
44
+ export { tokenize } from './tokenize.js';
45
+ export { evaluate, formatValue } from './evaluate.js';
46
+ export { FUNCTIONS, withCustomFunctions, } from './functions.js';
47
+ export { translateFormula, fixupReferences, renameSheetReferences, formatFormula, referenceSpans, REFERENCE_COLOURS, } from './refs.js';
48
+ export { createDependencyGraph, precedentsOf, cellKey, parseCellKey, } from './deps.js';
49
+ export { parseA1, formatA1, colToLetters, lettersToCol, } from './address.js';
50
+ export { isError, } from './ast.js';
51
+ export { SHEET_BINDINGS, handleSheetKey, } from './shortcuts.js';
52
+ export { edgeOfRegion, currentRegion, isWholeSheet, wholeSheet, isBlankValue, } from './navigate.js';
53
+ export { fillDown, fillRight, fillSelection, stampDate, stampNow, copyFromAbove, copyValueFromAbove, guessSumRange, looksNumeric, numericAt, targetRect, setFillTranslator, setSheetValueProbe, getSheetValueProbe, } from './commands.js';
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Merged cells in the sheet: Excel's Merge & Center, Merge Across, Merge
3
+ * Cells and Unmerge Cells over the document's per-sheet `merges`, which
4
+ * are rectangles `[minRow, minCol, maxRow, maxCol]`. The grid draws them
5
+ * through its `mergedCells` prop; this module is the pure part: what a
6
+ * command does to the rectangles and to the cells under them.
7
+ *
8
+ * As in Excel, a merge keeps the top-left cell's value and drops the
9
+ * rest, so `mergePlan` says which cells to clear; the shell writes them
10
+ * through the command context so the merge and the clears are one undo.
11
+ */
12
+ import { type Rect } from './rects.js';
13
+ export type MergeKind = 'center' | 'across' | 'cells';
14
+ export type MergePlan = {
15
+ /** The rectangles after the command. */
16
+ merges: Rect[];
17
+ /** Cells to clear, top-left cells of each new merge excluded. */
18
+ clear: Array<[row: number, col: number]>;
19
+ /** Whether Merge & Center also centres: the shell sets `align`. */
20
+ center: boolean;
21
+ /** The new merges' top-left cells, where the centre alignment goes. */
22
+ origins: Array<[row: number, col: number]>;
23
+ };
24
+ /** Normalise a rectangle to top-left / bottom-right. */
25
+ export declare function normalRect(rect: Rect): Rect;
26
+ /** The merges touching any of the rectangles. */
27
+ export declare function mergesIn(merges: ReadonlyArray<Rect>, rects: ReadonlyArray<Rect>): Rect[];
28
+ /** The merge covering (r, c), or undefined. */
29
+ export declare function mergeAt(merges: ReadonlyArray<Rect>, r: number, c: number): Rect | undefined;
30
+ /** Whether (r, c) is inside a merge without being its top-left cell. */
31
+ export declare function isCoveredCell(merges: ReadonlyArray<Rect>, r: number, c: number): boolean;
32
+ /**
33
+ * Merge the selection: one merge per rectangle (Merge & Center, Merge
34
+ * Cells) or one per row of each rectangle (Merge Across). Merges already
35
+ * inside the selection are replaced, as Excel replaces them. A single
36
+ * cell merges nothing.
37
+ */
38
+ export declare function mergePlan(merges: ReadonlyArray<Rect>, rects: ReadonlyArray<Rect>, kind: MergeKind, isBlank: (r: number, c: number) => boolean): MergePlan;
39
+ /**
40
+ * Whether merging the selection would drop a value: Excel warns that
41
+ * "merging cells only keeps the upper-left value". The shell asks before
42
+ * it merges.
43
+ */
44
+ export declare function mergeDropsValues(rects: ReadonlyArray<Rect>, kind: MergeKind, isBlank: (r: number, c: number) => boolean): boolean;
45
+ /** Unmerge: every merge the selection touches goes. */
46
+ export declare function unmergePlan(merges: ReadonlyArray<Rect>, rects: ReadonlyArray<Rect>): Rect[];
47
+ /**
48
+ * A rectangle grown to hold every merge it touches, to a fixed point: the
49
+ * grid selects this way (a click on a merged cell takes the whole merge),
50
+ * so the header shading and anything else that reads the selection's
51
+ * columns and rows has to see the same rectangle.
52
+ */
53
+ export declare function expandToMerges(merges: ReadonlyArray<Rect>, rect: Rect): Rect;
54
+ /** Whether the selection holds a merge, for the button's state. */
55
+ export declare function selectionMerged(merges: ReadonlyArray<Rect>, rects: ReadonlyArray<Rect>): boolean;
56
+ /** The merges as the grid takes them. */
57
+ export declare function toGridMerges(merges: ReadonlyArray<Rect>): Array<{
58
+ rowIndex: number;
59
+ colIndex: number;
60
+ rowSpan: number;
61
+ colSpan: number;
62
+ }>;
63
+ /**
64
+ * Whether a sort of `rect` can go ahead: Excel refuses when a merge
65
+ * crosses the range's edge ("all merged cells need to be the same size"),
66
+ * and the shell moves whole merges with their rows only when every merge
67
+ * in the range is one row tall and sits inside it.
68
+ */
69
+ export declare function sortBlockedByMerges(merges: ReadonlyArray<Rect>, rect: Rect): boolean;
70
+ /** The merges of a sorted region, each moved with the row it sat on. */
71
+ export declare function reorderMerges(merges: ReadonlyArray<Rect>, rect: Rect, rowFor: (oldRow: number) => number): Rect[];
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Merged cells in the sheet: Excel's Merge & Center, Merge Across, Merge
3
+ * Cells and Unmerge Cells over the document's per-sheet `merges`, which
4
+ * are rectangles `[minRow, minCol, maxRow, maxCol]`. The grid draws them
5
+ * through its `mergedCells` prop; this module is the pure part: what a
6
+ * command does to the rectangles and to the cells under them.
7
+ *
8
+ * As in Excel, a merge keeps the top-left cell's value and drops the
9
+ * rest, so `mergePlan` says which cells to clear; the shell writes them
10
+ * through the command context so the merge and the clears are one undo.
11
+ */
12
+ import { rectContains, rectsIntersect } from './rects.js';
13
+ /** Normalise a rectangle to top-left / bottom-right. */
14
+ export function normalRect(rect) {
15
+ return [Math.min(rect[0], rect[2]), Math.min(rect[1], rect[3]), Math.max(rect[0], rect[2]), Math.max(rect[1], rect[3])];
16
+ }
17
+ /** The merges touching any of the rectangles. */
18
+ export function mergesIn(merges, rects) {
19
+ return merges.filter((m) => rects.some((r) => rectsIntersect(m, normalRect(r))));
20
+ }
21
+ /** The merge covering (r, c), or undefined. */
22
+ export function mergeAt(merges, r, c) {
23
+ return merges.find((m) => rectContains(m, r, c));
24
+ }
25
+ /** Whether (r, c) is inside a merge without being its top-left cell. */
26
+ export function isCoveredCell(merges, r, c) {
27
+ const m = mergeAt(merges, r, c);
28
+ return !!m && (m[0] !== r || m[1] !== c);
29
+ }
30
+ /**
31
+ * Merge the selection: one merge per rectangle (Merge & Center, Merge
32
+ * Cells) or one per row of each rectangle (Merge Across). Merges already
33
+ * inside the selection are replaced, as Excel replaces them. A single
34
+ * cell merges nothing.
35
+ */
36
+ export function mergePlan(merges, rects, kind, isBlank) {
37
+ const targets = [];
38
+ for (const raw of rects) {
39
+ const rect = normalRect(raw);
40
+ if (kind === 'across') {
41
+ if (rect[1] === rect[3])
42
+ continue;
43
+ for (let r = rect[0]; r <= rect[2]; r += 1)
44
+ targets.push([r, rect[1], r, rect[3]]);
45
+ }
46
+ else if (rect[0] !== rect[2] || rect[1] !== rect[3]) {
47
+ targets.push(rect);
48
+ }
49
+ }
50
+ const kept = merges.filter((m) => !targets.some((t) => rectsIntersect(m, t)));
51
+ const clear = [];
52
+ const origins = [];
53
+ for (const t of targets) {
54
+ origins.push([t[0], t[1]]);
55
+ for (let r = t[0]; r <= t[2]; r += 1) {
56
+ for (let c = t[1]; c <= t[3]; c += 1) {
57
+ if (r === t[0] && c === t[1])
58
+ continue;
59
+ if (!isBlank(r, c))
60
+ clear.push([r, c]);
61
+ }
62
+ }
63
+ }
64
+ return { merges: [...kept, ...targets], clear, center: kind === 'center', origins };
65
+ }
66
+ /**
67
+ * Whether merging the selection would drop a value: Excel warns that
68
+ * "merging cells only keeps the upper-left value". The shell asks before
69
+ * it merges.
70
+ */
71
+ export function mergeDropsValues(rects, kind, isBlank) {
72
+ return mergePlan([], rects, kind, isBlank).clear.length > 0;
73
+ }
74
+ /** Unmerge: every merge the selection touches goes. */
75
+ export function unmergePlan(merges, rects) {
76
+ const touched = mergesIn(merges, rects);
77
+ return merges.filter((m) => !touched.includes(m));
78
+ }
79
+ /**
80
+ * A rectangle grown to hold every merge it touches, to a fixed point: the
81
+ * grid selects this way (a click on a merged cell takes the whole merge),
82
+ * so the header shading and anything else that reads the selection's
83
+ * columns and rows has to see the same rectangle.
84
+ */
85
+ export function expandToMerges(merges, rect) {
86
+ let out = normalRect(rect);
87
+ for (let guard = 0; guard < 64; guard += 1) {
88
+ let grown = false;
89
+ for (const m of merges) {
90
+ if (!rectsIntersect(m, out))
91
+ continue;
92
+ const next = [Math.min(out[0], m[0]), Math.min(out[1], m[1]), Math.max(out[2], m[2]), Math.max(out[3], m[3])];
93
+ if (next[0] !== out[0] || next[1] !== out[1] || next[2] !== out[2] || next[3] !== out[3]) {
94
+ out = next;
95
+ grown = true;
96
+ }
97
+ }
98
+ if (!grown)
99
+ break;
100
+ }
101
+ return out;
102
+ }
103
+ /** Whether the selection holds a merge, for the button's state. */
104
+ export function selectionMerged(merges, rects) {
105
+ return mergesIn(merges, rects).length > 0;
106
+ }
107
+ /** The merges as the grid takes them. */
108
+ export function toGridMerges(merges) {
109
+ return merges.map((m) => ({ rowIndex: m[0], colIndex: m[1], rowSpan: m[2] - m[0] + 1, colSpan: m[3] - m[1] + 1 }));
110
+ }
111
+ /**
112
+ * Whether a sort of `rect` can go ahead: Excel refuses when a merge
113
+ * crosses the range's edge ("all merged cells need to be the same size"),
114
+ * and the shell moves whole merges with their rows only when every merge
115
+ * in the range is one row tall and sits inside it.
116
+ */
117
+ export function sortBlockedByMerges(merges, rect) {
118
+ const range = normalRect(rect);
119
+ for (const m of merges) {
120
+ if (!rectsIntersect(m, range))
121
+ continue;
122
+ const inside = m[0] >= range[0] && m[2] <= range[2] && m[1] >= range[1] && m[3] <= range[3];
123
+ if (!inside || m[0] !== m[2])
124
+ return true;
125
+ }
126
+ return false;
127
+ }
128
+ /** The merges of a sorted region, each moved with the row it sat on. */
129
+ export function reorderMerges(merges, rect, rowFor) {
130
+ const range = normalRect(rect);
131
+ return merges.map((m) => {
132
+ if (!rectsIntersect(m, range))
133
+ return m;
134
+ const row = rowFor(m[0]);
135
+ return [row, m[1], row, m[3]];
136
+ });
137
+ }
@@ -0,0 +1,31 @@
1
+ import type { Node } from './ast.js';
2
+ export type DefinedName = {
3
+ name: string;
4
+ /** What it refers to, e.g. `=Sheet1!$B$1` or `=$A$1:$C$9`. */
5
+ refersTo: string;
6
+ };
7
+ export type SheetNames = {
8
+ define(name: string, refersTo: string): void;
9
+ remove(name: string): void;
10
+ has(name: string): boolean;
11
+ /** The parsed reference behind a name, or null when undefined or unparseable. */
12
+ resolve(name: string): Node | null;
13
+ list(): DefinedName[];
14
+ serialize(): Record<string, string>;
15
+ hydrate(entries: Record<string, string>): void;
16
+ clear(): void;
17
+ };
18
+ /**
19
+ * Excel's rules, and the ones worth enforcing because a name that looks like a
20
+ * reference makes every formula containing it ambiguous:
21
+ * - starts with a letter or underscore
22
+ * - letters, digits, underscores and dots after that
23
+ * - not something that reads as a CELL reference (`A1`, `$A$1`)
24
+ * - not `R` or `C`, which Excel reserves for R1C1 notation
25
+ *
26
+ * A bare word like `Tax` is fine even though `TAX` is a valid column label:
27
+ * the tokenizer only reads a word as a column when it is qualified by a sheet
28
+ * or followed by a colon, so `=Tax` is never ambiguous.
29
+ */
30
+ export declare function isValidName(name: string): boolean;
31
+ export declare function createNames(initial?: Record<string, string>): SheetNames;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Defined names: `Tax` resolving to `Sheet1!$B$1`, so a formula can say
3
+ * `=Subtotal*Tax` instead of `=D5*$B$1`.
4
+ *
5
+ * Names are stored as the TEXT they refer to rather than a parsed reference,
6
+ * because that is what the user typed and what round-trips to xlsx. Resolution
7
+ * parses on demand and caches nothing: names change rarely and resolving is
8
+ * cheap next to evaluating the formula that asked.
9
+ */
10
+ import { parseFormula } from './parse.js';
11
+ /**
12
+ * Excel's rules, and the ones worth enforcing because a name that looks like a
13
+ * reference makes every formula containing it ambiguous:
14
+ * - starts with a letter or underscore
15
+ * - letters, digits, underscores and dots after that
16
+ * - not something that reads as a CELL reference (`A1`, `$A$1`)
17
+ * - not `R` or `C`, which Excel reserves for R1C1 notation
18
+ *
19
+ * A bare word like `Tax` is fine even though `TAX` is a valid column label:
20
+ * the tokenizer only reads a word as a column when it is qualified by a sheet
21
+ * or followed by a colon, so `=Tax` is never ambiguous.
22
+ */
23
+ export function isValidName(name) {
24
+ if (!/^[A-Za-z_][A-Za-z0-9_.]*$/.test(name))
25
+ return false;
26
+ if (name.length > 255)
27
+ return false;
28
+ // Letters followed by digits is a cell reference, whatever else it spells.
29
+ if (/^\$?[A-Za-z]{1,3}\$?\d+$/.test(name))
30
+ return false;
31
+ if (/^[RC]$/i.test(name))
32
+ return false;
33
+ if (/^(TRUE|FALSE)$/i.test(name))
34
+ return false;
35
+ return true;
36
+ }
37
+ export function createNames(initial) {
38
+ // Names are case-insensitive in Excel but keep the case they were defined
39
+ // with, so the map keys on the uppercase form and the entry holds the
40
+ // original.
41
+ const byUpper = new Map();
42
+ const api = {
43
+ define(name, refersTo) {
44
+ if (!isValidName(name)) {
45
+ throw new Error(`"${name}" is not a valid defined name: it must start with a letter ` +
46
+ 'or underscore and must not look like a cell reference.');
47
+ }
48
+ byUpper.set(name.toUpperCase(), { name, refersTo });
49
+ },
50
+ remove(name) {
51
+ byUpper.delete(name.toUpperCase());
52
+ },
53
+ has(name) {
54
+ return byUpper.has(name.toUpperCase());
55
+ },
56
+ resolve(name) {
57
+ const entry = byUpper.get(name.toUpperCase());
58
+ if (!entry)
59
+ return null;
60
+ try {
61
+ return parseFormula(entry.refersTo);
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ },
67
+ list() {
68
+ return [...byUpper.values()].sort((a, b) => a.name.localeCompare(b.name));
69
+ },
70
+ serialize() {
71
+ return Object.fromEntries([...byUpper.values()].map((e) => [e.name, e.refersTo]));
72
+ },
73
+ hydrate(entries) {
74
+ byUpper.clear();
75
+ for (const [name, refersTo] of Object.entries(entries)) {
76
+ if (isValidName(name))
77
+ byUpper.set(name.toUpperCase(), { name, refersTo });
78
+ }
79
+ },
80
+ clear() {
81
+ byUpper.clear();
82
+ },
83
+ };
84
+ if (initial)
85
+ api.hydrate(initial);
86
+ return api;
87
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Excel's jump-to-edge navigation, as pure functions over a cell-value lookup.
3
+ *
4
+ * `Ctrl+Arrow` is not "move a long way". It is a run-boundary search, and the
5
+ * rule flips depending on what you are standing on:
6
+ *
7
+ * - Standing on a FILLED cell with a filled neighbour: run to the LAST filled
8
+ * cell before the next blank. This is how you get from the top of a column
9
+ * to its bottom in one press.
10
+ * - Standing on a FILLED cell whose neighbour is blank: skip the blank run
11
+ * and land on the NEXT filled cell. This is how you hop between blocks.
12
+ * - Standing on a BLANK cell: skip to the next filled cell.
13
+ * - Nothing filled ahead in either case: land on the last cell of the sheet
14
+ * in that direction, which is what Ctrl+Down does on an empty column.
15
+ *
16
+ * Getting this wrong in the obvious way (always run to the end of the filled
17
+ * block) means the shortcut stops working the moment there is a gap, which is
18
+ * most real spreadsheets.
19
+ */
20
+ /** What counts as empty. Excel treats a cell holding an empty string as blank
21
+ * for navigation, and so does this: a column of `''` should not trap the
22
+ * cursor. A zero, a `false` and a whitespace-only string are all content. */
23
+ export declare function isBlankValue(value: unknown): boolean;
24
+ export type Direction = 'up' | 'down' | 'left' | 'right';
25
+ export type Grid = {
26
+ rowCount: number;
27
+ colCount: number;
28
+ isBlank(row: number, col: number): boolean;
29
+ };
30
+ export type Cell = {
31
+ row: number;
32
+ col: number;
33
+ };
34
+ /**
35
+ * Where `Ctrl+Arrow` lands from `from`, going `dir`. Never returns a cell off
36
+ * the sheet, and returns `from` unchanged when it is already on the edge.
37
+ */
38
+ export declare function edgeOfRegion(grid: Grid, from: Cell, dir: Direction): Cell;
39
+ export type Rect = readonly [minRow: number, minCol: number, maxRow: number, maxCol: number];
40
+ /**
41
+ * Excel's `Ctrl+A` first press: the contiguous block of non-blank cells around
42
+ * `from`, grown outward until every bounding edge is blank.
43
+ *
44
+ * Grown as a rectangle rather than flood-filled, because that is what Excel
45
+ * selects: a block with a notch in it still comes back as the rectangle that
46
+ * encloses it. Returns the single cell when `from` sits alone in white space.
47
+ */
48
+ export declare function currentRegion(grid: Grid, from: Cell): Rect;
49
+ /** Whether `rect` already covers every cell, so `Ctrl+A` should stop growing. */
50
+ export declare function isWholeSheet(grid: Grid, rect: Rect): boolean;
51
+ /** The whole sheet as a rectangle. */
52
+ export declare function wholeSheet(grid: Grid): Rect;
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Excel's jump-to-edge navigation, as pure functions over a cell-value lookup.
3
+ *
4
+ * `Ctrl+Arrow` is not "move a long way". It is a run-boundary search, and the
5
+ * rule flips depending on what you are standing on:
6
+ *
7
+ * - Standing on a FILLED cell with a filled neighbour: run to the LAST filled
8
+ * cell before the next blank. This is how you get from the top of a column
9
+ * to its bottom in one press.
10
+ * - Standing on a FILLED cell whose neighbour is blank: skip the blank run
11
+ * and land on the NEXT filled cell. This is how you hop between blocks.
12
+ * - Standing on a BLANK cell: skip to the next filled cell.
13
+ * - Nothing filled ahead in either case: land on the last cell of the sheet
14
+ * in that direction, which is what Ctrl+Down does on an empty column.
15
+ *
16
+ * Getting this wrong in the obvious way (always run to the end of the filled
17
+ * block) means the shortcut stops working the moment there is a gap, which is
18
+ * most real spreadsheets.
19
+ */
20
+ /** What counts as empty. Excel treats a cell holding an empty string as blank
21
+ * for navigation, and so does this: a column of `''` should not trap the
22
+ * cursor. A zero, a `false` and a whitespace-only string are all content. */
23
+ export function isBlankValue(value) {
24
+ return value === null || value === undefined || value === '';
25
+ }
26
+ const STEP = {
27
+ up: { row: -1, col: 0 },
28
+ down: { row: 1, col: 0 },
29
+ left: { row: 0, col: -1 },
30
+ right: { row: 0, col: 1 },
31
+ };
32
+ function inBounds(grid, row, col) {
33
+ return row >= 0 && row < grid.rowCount && col >= 0 && col < grid.colCount;
34
+ }
35
+ /** The last cell in `dir` that is still on the sheet. */
36
+ function lastInDirection(grid, from, dir) {
37
+ switch (dir) {
38
+ case 'up': return { row: 0, col: from.col };
39
+ case 'down': return { row: Math.max(grid.rowCount - 1, 0), col: from.col };
40
+ case 'left': return { row: from.row, col: 0 };
41
+ case 'right': return { row: from.row, col: Math.max(grid.colCount - 1, 0) };
42
+ }
43
+ }
44
+ /**
45
+ * Where `Ctrl+Arrow` lands from `from`, going `dir`. Never returns a cell off
46
+ * the sheet, and returns `from` unchanged when it is already on the edge.
47
+ */
48
+ export function edgeOfRegion(grid, from, dir) {
49
+ if (grid.rowCount === 0 || grid.colCount === 0)
50
+ return from;
51
+ const step = STEP[dir];
52
+ const first = { row: from.row + step.row, col: from.col + step.col };
53
+ if (!inBounds(grid, first.row, first.col))
54
+ return from;
55
+ const startFilled = !grid.isBlank(from.row, from.col);
56
+ const nextFilled = !grid.isBlank(first.row, first.col);
57
+ if (startFilled && nextFilled) {
58
+ // Run to the last filled cell before the next blank.
59
+ let cur = first;
60
+ for (;;) {
61
+ const ahead = { row: cur.row + step.row, col: cur.col + step.col };
62
+ if (!inBounds(grid, ahead.row, ahead.col) || grid.isBlank(ahead.row, ahead.col))
63
+ return cur;
64
+ cur = ahead;
65
+ }
66
+ }
67
+ // Otherwise skip ahead to the next filled cell.
68
+ let cur = first;
69
+ for (;;) {
70
+ if (!grid.isBlank(cur.row, cur.col))
71
+ return cur;
72
+ const ahead = { row: cur.row + step.row, col: cur.col + step.col };
73
+ // Nothing filled the whole way: Excel parks you on the last cell.
74
+ if (!inBounds(grid, ahead.row, ahead.col))
75
+ return lastInDirection(grid, from, dir);
76
+ cur = ahead;
77
+ }
78
+ }
79
+ /**
80
+ * Excel's `Ctrl+A` first press: the contiguous block of non-blank cells around
81
+ * `from`, grown outward until every bounding edge is blank.
82
+ *
83
+ * Grown as a rectangle rather than flood-filled, because that is what Excel
84
+ * selects: a block with a notch in it still comes back as the rectangle that
85
+ * encloses it. Returns the single cell when `from` sits alone in white space.
86
+ */
87
+ export function currentRegion(grid, from) {
88
+ if (grid.rowCount === 0 || grid.colCount === 0)
89
+ return [0, 0, 0, 0];
90
+ let minRow = from.row;
91
+ let maxRow = from.row;
92
+ let minCol = from.col;
93
+ let maxCol = from.col;
94
+ const edgeHasContent = (r0, r1, c0, c1) => {
95
+ for (let r = r0; r <= r1; r += 1) {
96
+ for (let c = c0; c <= c1; c += 1) {
97
+ if (inBounds(grid, r, c) && !grid.isBlank(r, c))
98
+ return true;
99
+ }
100
+ }
101
+ return false;
102
+ };
103
+ // Keep growing while any bounding edge still touches content. One side
104
+ // growing can expose content on another, so this repeats until stable.
105
+ for (let grew = true; grew;) {
106
+ grew = false;
107
+ if (minRow > 0 && edgeHasContent(minRow - 1, minRow - 1, minCol, maxCol)) {
108
+ minRow -= 1;
109
+ grew = true;
110
+ }
111
+ if (maxRow < grid.rowCount - 1 && edgeHasContent(maxRow + 1, maxRow + 1, minCol, maxCol)) {
112
+ maxRow += 1;
113
+ grew = true;
114
+ }
115
+ if (minCol > 0 && edgeHasContent(minRow, maxRow, minCol - 1, minCol - 1)) {
116
+ minCol -= 1;
117
+ grew = true;
118
+ }
119
+ if (maxCol < grid.colCount - 1 && edgeHasContent(minRow, maxRow, maxCol + 1, maxCol + 1)) {
120
+ maxCol += 1;
121
+ grew = true;
122
+ }
123
+ }
124
+ return [minRow, minCol, maxRow, maxCol];
125
+ }
126
+ /** Whether `rect` already covers every cell, so `Ctrl+A` should stop growing. */
127
+ export function isWholeSheet(grid, rect) {
128
+ return (rect[0] === 0 && rect[1] === 0 &&
129
+ rect[2] === Math.max(grid.rowCount - 1, 0) &&
130
+ rect[3] === Math.max(grid.colCount - 1, 0));
131
+ }
132
+ /** The whole sheet as a rectangle. */
133
+ export function wholeSheet(grid) {
134
+ return [0, 0, Math.max(grid.rowCount - 1, 0), Math.max(grid.colCount - 1, 0)];
135
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Excel number-format strings, compiled once and applied many times.
3
+ *
4
+ * The grammar is small but full of traps, so this file is explicit about the
5
+ * ones that bite:
6
+ *
7
+ * - A pattern has up to four sections separated by `;`:
8
+ * positive;negative;zero;text. With two, the second covers negatives AND
9
+ * zero. With one, it covers everything. A negative rendered by section two
10
+ * uses its ABSOLUTE value, which is why `0.00;(0.00)` shows `(1.50)` and
11
+ * not `(-1.50)`.
12
+ * - `,` means two different things. Between placeholders it is a thousands
13
+ * separator; immediately before the decimal point (or at the end) it scales
14
+ * the number down by a thousand per comma.
15
+ * - `%` scales UP by 100 and prints a literal percent.
16
+ * - `0` prints a digit or a zero, `#` prints a digit or nothing, `?` prints a
17
+ * digit or a space. They can be mixed: `#,##0.0#` keeps one decimal always
18
+ * and a second only when it is there.
19
+ * - Anything in quotes, after a backslash, or in the set of always-literal
20
+ * characters is emitted as-is.
21
+ * - `[Red]` and friends set a colour rather than printing.
22
+ * - `@` is the text placeholder and only appears in the fourth section.
23
+ *
24
+ * Dates are detected rather than configured: a pattern containing date tokens
25
+ * outside quotes is a date pattern. `m` is minutes when it follows an hour
26
+ * token and months otherwise, which is the one piece of context the token
27
+ * scanner has to carry.
28
+ */
29
+ export type CompiledFormat = {
30
+ /** Render a value. `color` is set when the matching section named one. */
31
+ format(value: unknown): {
32
+ text: string;
33
+ color?: string;
34
+ };
35
+ /** The pattern this was compiled from, for round-tripping to xlsx. */
36
+ readonly pattern: string;
37
+ };
38
+ export declare function compileNumberFormat(pattern: string): CompiledFormat;
39
+ /** Format one value against a pattern. Convenience over compile-then-call. */
40
+ export declare function formatWithPattern(value: unknown, pattern: string): string;
41
+ /** The presets behind Ctrl+Shift+1 through 6. */
42
+ export declare const FORMAT_PRESETS: {
43
+ readonly general: "General";
44
+ readonly number: "#,##0.00";
45
+ readonly time: "h:mm AM/PM";
46
+ readonly date: "yyyy-mm-dd";
47
+ readonly currency: "$#,##0.00;($#,##0.00)";
48
+ readonly percent: "0.00%";
49
+ readonly scientific: "0.00E+00";
50
+ };
51
+ export type FormatPresetName = keyof typeof FORMAT_PRESETS;
52
+ /**
53
+ * Which of Excel's categories a pattern belongs to, with the decimals and
54
+ * separator it carries, so the ribbon's combo can say "Percentage" for a
55
+ * cell holding `0%` (a typed 12%) as well as for the preset's `0.00%`, and
56
+ * Format Cells can open on the right category. A pattern that is none of
57
+ * them is 'custom'.
58
+ */
59
+ export declare function formatCategory(fmt: string | undefined): {
60
+ category: FormatPresetName | 'custom';
61
+ decimals: number;
62
+ thousands: boolean;
63
+ };