@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,87 @@
1
+ /**
2
+ * Insert and delete rows and columns, with every formula on the sheet
3
+ * rewritten to follow.
4
+ *
5
+ * The api half already existed (`addRow`, `removeRow`, `addColumn`,
6
+ * `removeColumn`). What was missing is the fixup pass: inserting a row above
7
+ * `=SUM(D2:D11)` without widening the range silently produces the wrong total,
8
+ * and deleting a referenced row leaves a formula pointing at whatever moved
9
+ * into its place. Neither shows an error. That is why this waited for the
10
+ * engine rather than shipping with the other shortcuts.
11
+ *
12
+ * A structural edit is three things in order:
13
+ * 1. rewrite every formula in the sheet, and every defined name
14
+ * 2. drop format entries for whatever the edit deletes
15
+ * 3. apply the structural change itself
16
+ *
17
+ * Order matters: the first two read the OLD geometry.
18
+ */
19
+ import type { GridCommandContext } from '@svgrid/grid/shortcuts';
20
+ import { type StructuralEdit } from './refs.js';
21
+ import type { SheetNames } from './names.js';
22
+ import type { SheetFormatStore, CellAddressLookup } from './format-store.js';
23
+ /** What a structural command needs beyond the command context. */
24
+ export type StructureTarget = {
25
+ /** Rewrite one cell's raw text. Called only for cells that changed. */
26
+ setRaw(rowIndex: number, colIndex: number, text: string): void;
27
+ /** Read one cell's raw text, formula included. */
28
+ getRaw(rowIndex: number, colIndex: number): string;
29
+ /** Apply the structural change to the data itself. */
30
+ apply(edit: StructuralEdit): void;
31
+ /**
32
+ * Set when `apply` rewrites the references itself. A Workbook does, across
33
+ * every sheet, which is the whole reason a shell routes the edit through
34
+ * it; rewriting here as well then moved every reference twice, so one
35
+ * inserted row turned =SUM(A1:A3) into =SUM(A1:A5) and a cycle.
36
+ */
37
+ rewritesReferences?: boolean;
38
+ names?: SheetNames;
39
+ format?: {
40
+ store: SheetFormatStore;
41
+ lookup: CellAddressLookup;
42
+ };
43
+ onChange?(): void;
44
+ /**
45
+ * Everything a structural edit can touch, as one value, and the way to
46
+ * put it back. Supplying both makes insert and delete ONE Ctrl+Z: the
47
+ * command records the state before and after and hands the grid a step
48
+ * that restores either. Without them the formula rewrites inside the
49
+ * edit would sit in the history on their own, and undoing those while
50
+ * the rows stayed inserted would leave the sheet inconsistent.
51
+ */
52
+ snapshot?(): unknown;
53
+ restore?(state: unknown): void;
54
+ /**
55
+ * Whether the edit may happen at all. A protected sheet says no to every
56
+ * insert and delete, as Excel's does; the ribbon greys Insert and Delete
57
+ * on the same answer, so it must be cheap and must not talk to the user.
58
+ */
59
+ canApply?(edit: StructuralEdit): boolean;
60
+ /** Called when `canApply` refused an edit: the place to say why. */
61
+ refused?(): void;
62
+ };
63
+ /** Attach what the insert / delete commands operate on. */
64
+ export declare function setStructureTarget(next: StructureTarget | null): void;
65
+ export declare function getStructureTarget(): StructureTarget | null;
66
+ /**
67
+ * Rewrite every formula on the sheet for one structural edit.
68
+ *
69
+ * Walks the whole grid rather than consulting the dependency graph, because
70
+ * the graph indexes what a formula READS and this needs every formula that
71
+ * MENTIONS a moved coordinate, including ones whose precedents were never
72
+ * resolved (a `#REF!` today may become valid after an insert).
73
+ */
74
+ export declare function rewriteFormulas(cmd: GridCommandContext, t: StructureTarget, edit: StructuralEdit): number;
75
+ export declare function insertRows(cmd: GridCommandContext, at?: number, count?: number): boolean;
76
+ export declare function deleteRows(cmd: GridCommandContext, at?: number, count?: number): boolean;
77
+ export declare function insertColumns(cmd: GridCommandContext, at?: number, count?: number): boolean;
78
+ export declare function deleteColumns(cmd: GridCommandContext, at?: number, count?: number): boolean;
79
+ /**
80
+ * Which axis a bare Ctrl+Plus / Ctrl+Minus acts on.
81
+ *
82
+ * Excel opens a dialog when the selection is not a whole row or column. That
83
+ * dialog is a design decision, so this reports what it would ask about and
84
+ * lets the consumer decide: a selection spanning every column is a row
85
+ * operation, every row is a column operation, anything else is ambiguous.
86
+ */
87
+ export declare function axisForSelection(cmd: GridCommandContext): 'rows' | 'cols' | 'ambiguous';
@@ -0,0 +1,166 @@
1
+ import { fixupReferences } from './refs.js';
2
+ let target = null;
3
+ /** Attach what the insert / delete commands operate on. */
4
+ export function setStructureTarget(next) {
5
+ target = next;
6
+ }
7
+ export function getStructureTarget() {
8
+ return target;
9
+ }
10
+ /**
11
+ * Rewrite every formula on the sheet for one structural edit.
12
+ *
13
+ * Walks the whole grid rather than consulting the dependency graph, because
14
+ * the graph indexes what a formula READS and this needs every formula that
15
+ * MENTIONS a moved coordinate, including ones whose precedents were never
16
+ * resolved (a `#REF!` today may become valid after an insert).
17
+ */
18
+ export function rewriteFormulas(cmd, t, edit) {
19
+ let changed = 0;
20
+ for (let r = 0; r < cmd.rowCount; r += 1) {
21
+ for (let c = 0; c < cmd.colCount; c += 1) {
22
+ const text = t.getRaw(r, c);
23
+ if (!text.startsWith('='))
24
+ continue;
25
+ const next = fixupReferences(text, edit);
26
+ if (typeof next === 'string' && next !== text) {
27
+ t.setRaw(r, c, next);
28
+ changed += 1;
29
+ }
30
+ }
31
+ }
32
+ if (t.names) {
33
+ for (const entry of t.names.list()) {
34
+ const next = fixupReferences(entry.refersTo, edit);
35
+ if (typeof next === 'string' && next !== entry.refersTo) {
36
+ t.names.define(entry.name, next);
37
+ changed += 1;
38
+ }
39
+ }
40
+ }
41
+ return changed;
42
+ }
43
+ /**
44
+ * Drop format entries for rows or columns the edit removes.
45
+ *
46
+ * Nothing needs SHIFTING. The store keys on row and column IDS, and an id
47
+ * travels with the thing it names: inserting a column at B does not rename
48
+ * what was B, it just puts a new id before it, so every existing entry still
49
+ * points at the right cell. That is the payoff for keying on ids instead of
50
+ * indices, and it is the same property that makes the store survive a sort.
51
+ *
52
+ * A DELETE is different: those ids are gone, and their entries would sit in
53
+ * the map for the life of the session. Hence this.
54
+ */
55
+ function forgetDeleted(t, edit, cmd) {
56
+ const format = t.format;
57
+ if (!format)
58
+ return;
59
+ const rows = edit.kind === 'deleteRows';
60
+ const cols = edit.kind === 'deleteCols';
61
+ if (!rows && !cols)
62
+ return;
63
+ const limit = rows ? cmd.rowCount : cmd.colCount;
64
+ for (let i = edit.at; i < Math.min(edit.at + edit.count, limit); i += 1) {
65
+ const id = rows ? format.lookup.rowIdAt(i) : format.lookup.columnIdAt(i);
66
+ if (id === null)
67
+ continue;
68
+ if (rows)
69
+ format.store.forgetRow(id);
70
+ else
71
+ format.store.forgetColumn(id);
72
+ }
73
+ }
74
+ function run(cmd, edit) {
75
+ const t = target;
76
+ if (!t)
77
+ return false;
78
+ if (edit.count <= 0)
79
+ return false;
80
+ if (t.canApply?.(edit) === false) {
81
+ t.refused?.();
82
+ return false;
83
+ }
84
+ return cmd.batch(() => {
85
+ const before = t.snapshot?.();
86
+ // Order matters: the rewrite and the cleanup both read the OLD geometry.
87
+ if (!t.rewritesReferences)
88
+ rewriteFormulas(cmd, t, edit);
89
+ forgetDeleted(t, edit, cmd);
90
+ t.apply(edit);
91
+ t.onChange?.();
92
+ // Recorded LAST, so undo runs it first: the whole state comes back, and
93
+ // the rewrite steps recorded above then re-apply their own "before"
94
+ // texts, which the snapshot already holds. Redo runs them the other way
95
+ // round to the same end.
96
+ const restore = t.restore;
97
+ if (before !== undefined && restore) {
98
+ const after = t.snapshot?.();
99
+ cmd.recordUndo?.(() => { restore(before); t.onChange?.(); }, () => { restore(after); t.onChange?.(); });
100
+ }
101
+ return true;
102
+ });
103
+ }
104
+ /** The span the active selection covers, as whole rows or whole columns. */
105
+ function spanOf(cmd, axis) {
106
+ const rect = cmd.ranges[cmd.ranges.length - 1];
107
+ if (rect) {
108
+ const [minRow, minCol, maxRow, maxCol] = rect;
109
+ return axis === 'rows'
110
+ ? { at: minRow, count: maxRow - minRow + 1 }
111
+ : { at: minCol, count: maxCol - minCol + 1 };
112
+ }
113
+ const active = cmd.activeCell;
114
+ if (!active)
115
+ return null;
116
+ return axis === 'rows'
117
+ ? { at: active.rowIndex, count: 1 }
118
+ : { at: active.colIndex, count: 1 };
119
+ }
120
+ export function insertRows(cmd, at, count = 1) {
121
+ const span = at === undefined ? spanOf(cmd, 'rows') : { at, count };
122
+ if (!span)
123
+ return false;
124
+ return run(cmd, { kind: 'insertRows', at: span.at, count: span.count });
125
+ }
126
+ export function deleteRows(cmd, at, count = 1) {
127
+ const span = at === undefined ? spanOf(cmd, 'rows') : { at, count };
128
+ if (!span)
129
+ return false;
130
+ return run(cmd, { kind: 'deleteRows', at: span.at, count: span.count });
131
+ }
132
+ export function insertColumns(cmd, at, count = 1) {
133
+ const span = at === undefined ? spanOf(cmd, 'cols') : { at, count };
134
+ if (!span)
135
+ return false;
136
+ return run(cmd, { kind: 'insertCols', at: span.at, count: span.count });
137
+ }
138
+ export function deleteColumns(cmd, at, count = 1) {
139
+ const span = at === undefined ? spanOf(cmd, 'cols') : { at, count };
140
+ if (!span)
141
+ return false;
142
+ return run(cmd, { kind: 'deleteCols', at: span.at, count: span.count });
143
+ }
144
+ /**
145
+ * Which axis a bare Ctrl+Plus / Ctrl+Minus acts on.
146
+ *
147
+ * Excel opens a dialog when the selection is not a whole row or column. That
148
+ * dialog is a design decision, so this reports what it would ask about and
149
+ * lets the consumer decide: a selection spanning every column is a row
150
+ * operation, every row is a column operation, anything else is ambiguous.
151
+ */
152
+ export function axisForSelection(cmd) {
153
+ const rect = cmd.ranges[cmd.ranges.length - 1];
154
+ if (!rect)
155
+ return 'rows';
156
+ const [minRow, minCol, maxRow, maxCol] = rect;
157
+ const spansAllCols = minCol === 0 && maxCol >= cmd.colCount - 1;
158
+ const spansAllRows = minRow === 0 && maxRow >= cmd.rowCount - 1;
159
+ if (spansAllCols && !spansAllRows)
160
+ return 'rows';
161
+ if (spansAllRows && !spansAllCols)
162
+ return 'cols';
163
+ if (spansAllCols && spansAllRows)
164
+ return 'rows';
165
+ return 'ambiguous';
166
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Tables: a named region with a header row, so a formula can say
3
+ * `Orders[Amount]` instead of `D2:D57` and keep meaning it after rows are
4
+ * added.
5
+ *
6
+ * The point is not shorter text. A1 references are positional, so a total
7
+ * under a table has to be re-pointed every time the table grows, and a
8
+ * reference typed against 57 rows silently stops covering row 58. A
9
+ * structured reference names the COLUMN, and the range it resolves to is
10
+ * whatever the table currently is.
11
+ *
12
+ * This module is the model; the grammar that reads `Orders[Amount]` lives in
13
+ * the tokenizer, and resolving one to a range happens at evaluation, because
14
+ * the range depends on the table's size at that moment rather than at parse
15
+ * time.
16
+ */
17
+ export type TableRegion = {
18
+ name: string;
19
+ sheet: string;
20
+ /** Row of the header. The data starts on the next one. */
21
+ headerRow: number;
22
+ /** First and last column, inclusive. */
23
+ firstCol: number;
24
+ lastCol: number;
25
+ /** Last row of the DATA, not counting a totals row. */
26
+ lastRow: number;
27
+ /** Whether the row after `lastRow` is a totals row. */
28
+ hasTotals: boolean;
29
+ };
30
+ /** Which part of a table a structured reference asks for. */
31
+ export type TableSpecifier = '#All' | '#Data' | '#Headers' | '#Totals' | '#ThisRow';
32
+ export type TableRange = {
33
+ sheet: string;
34
+ firstRow: number;
35
+ lastRow: number;
36
+ firstCol: number;
37
+ lastCol: number;
38
+ };
39
+ export type TableRegistry = {
40
+ define(table: TableRegion): void;
41
+ remove(name: string): boolean;
42
+ get(name: string): TableRegion | undefined;
43
+ list(): TableRegion[];
44
+ /** The table containing a cell, if any. This is what makes an unqualified
45
+ * `[@Amount]` work: it only means something inside a table. */
46
+ at(sheet: string, row: number, col: number): TableRegion | undefined;
47
+ /** Grow a table to include a row written just below it, the way Excel's
48
+ * auto-expand does. Returns whether anything changed. */
49
+ growToInclude(sheet: string, row: number, col: number): boolean;
50
+ clear(): void;
51
+ };
52
+ /** Excel's rules for a table name are the same as for a defined name. */
53
+ export declare function isValidTableName(name: string): boolean;
54
+ export declare function createTableRegistry(initial?: ReadonlyArray<TableRegion>): TableRegistry;
55
+ /** Column index for a header name, or -1. Case-insensitive, as Excel is. */
56
+ export declare function columnIndexOf(table: TableRegion, header: string, headerAt: (sheet: string, row: number, col: number) => string): number;
57
+ /**
58
+ * Resolve a structured reference to a rectangle.
59
+ *
60
+ * `currentRow` is where the formula lives, which is the only way `[@Amount]`
61
+ * can mean anything: "this row" is relative to the formula, not the table.
62
+ * A `#ThisRow` reference from outside the table has no answer, so it returns
63
+ * null and the evaluator reports #VALUE!, which is what Excel does.
64
+ */
65
+ export declare function resolveTableRange(table: TableRegion, specifier: TableSpecifier, columns: {
66
+ from: number;
67
+ to: number;
68
+ } | null, currentRow: number | null): TableRange | null;
69
+ /** How many data rows a table holds. */
70
+ export declare function rowCountOf(table: TableRegion): number;
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Tables: a named region with a header row, so a formula can say
3
+ * `Orders[Amount]` instead of `D2:D57` and keep meaning it after rows are
4
+ * added.
5
+ *
6
+ * The point is not shorter text. A1 references are positional, so a total
7
+ * under a table has to be re-pointed every time the table grows, and a
8
+ * reference typed against 57 rows silently stops covering row 58. A
9
+ * structured reference names the COLUMN, and the range it resolves to is
10
+ * whatever the table currently is.
11
+ *
12
+ * This module is the model; the grammar that reads `Orders[Amount]` lives in
13
+ * the tokenizer, and resolving one to a range happens at evaluation, because
14
+ * the range depends on the table's size at that moment rather than at parse
15
+ * time.
16
+ */
17
+ /** Excel's rules for a table name are the same as for a defined name. */
18
+ export function isValidTableName(name) {
19
+ if (!/^[A-Za-z_][A-Za-z0-9_.]*$/.test(name))
20
+ return false;
21
+ if (name.length > 255)
22
+ return false;
23
+ if (/^\$?[A-Za-z]{1,3}\$?\d+$/.test(name))
24
+ return false;
25
+ if (/^[RC]$/i.test(name))
26
+ return false;
27
+ return true;
28
+ }
29
+ export function createTableRegistry(initial = []) {
30
+ const byName = new Map();
31
+ const registry = {
32
+ define(table) {
33
+ if (!isValidTableName(table.name)) {
34
+ throw new Error(`"${table.name}" is not a valid table name`);
35
+ }
36
+ byName.set(table.name.toLowerCase(), { ...table });
37
+ },
38
+ remove(name) {
39
+ return byName.delete(name.toLowerCase());
40
+ },
41
+ get(name) {
42
+ return byName.get(name.toLowerCase());
43
+ },
44
+ list() {
45
+ return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
46
+ },
47
+ at(sheet, row, col) {
48
+ for (const table of byName.values()) {
49
+ if (table.sheet !== sheet)
50
+ continue;
51
+ if (col < table.firstCol || col > table.lastCol)
52
+ continue;
53
+ const bottom = table.hasTotals ? table.lastRow + 1 : table.lastRow;
54
+ if (row < table.headerRow || row > bottom)
55
+ continue;
56
+ return table;
57
+ }
58
+ return undefined;
59
+ },
60
+ growToInclude(sheet, row, col) {
61
+ for (const table of byName.values()) {
62
+ if (table.sheet !== sheet)
63
+ continue;
64
+ if (col < table.firstCol || col > table.lastCol)
65
+ continue;
66
+ // Only the row immediately after the data extends it. A write five
67
+ // rows below is a separate thing the user put there, and swallowing
68
+ // it would be worse than not expanding.
69
+ if (row !== table.lastRow + 1)
70
+ continue;
71
+ // A totals row occupies that slot already; Excel inserts above it,
72
+ // which is a structural edit rather than a grow.
73
+ if (table.hasTotals)
74
+ continue;
75
+ table.lastRow = row;
76
+ return true;
77
+ }
78
+ return false;
79
+ },
80
+ clear() {
81
+ byName.clear();
82
+ },
83
+ };
84
+ for (const table of initial)
85
+ registry.define(table);
86
+ return registry;
87
+ }
88
+ /** Column index for a header name, or -1. Case-insensitive, as Excel is. */
89
+ export function columnIndexOf(table, header, headerAt) {
90
+ const wanted = header.trim().toLowerCase();
91
+ for (let c = table.firstCol; c <= table.lastCol; c += 1) {
92
+ if (headerAt(table.sheet, table.headerRow, c).trim().toLowerCase() === wanted)
93
+ return c;
94
+ }
95
+ return -1;
96
+ }
97
+ /**
98
+ * Resolve a structured reference to a rectangle.
99
+ *
100
+ * `currentRow` is where the formula lives, which is the only way `[@Amount]`
101
+ * can mean anything: "this row" is relative to the formula, not the table.
102
+ * A `#ThisRow` reference from outside the table has no answer, so it returns
103
+ * null and the evaluator reports #VALUE!, which is what Excel does.
104
+ */
105
+ export function resolveTableRange(table, specifier, columns, currentRow) {
106
+ const firstCol = columns ? columns.from : table.firstCol;
107
+ const lastCol = columns ? columns.to : table.lastCol;
108
+ if (firstCol < 0 || lastCol < 0)
109
+ return null;
110
+ const totalsRow = table.hasTotals ? table.lastRow + 1 : null;
111
+ switch (specifier) {
112
+ case '#Headers':
113
+ return { sheet: table.sheet, firstRow: table.headerRow, lastRow: table.headerRow, firstCol, lastCol };
114
+ case '#Totals':
115
+ return totalsRow === null
116
+ ? null
117
+ : { sheet: table.sheet, firstRow: totalsRow, lastRow: totalsRow, firstCol, lastCol };
118
+ case '#All':
119
+ return {
120
+ sheet: table.sheet,
121
+ firstRow: table.headerRow,
122
+ lastRow: totalsRow ?? table.lastRow,
123
+ firstCol, lastCol,
124
+ };
125
+ case '#ThisRow': {
126
+ if (currentRow === null)
127
+ return null;
128
+ // Outside the table's data rows, "this row" is meaningless.
129
+ if (currentRow <= table.headerRow || currentRow > table.lastRow)
130
+ return null;
131
+ return { sheet: table.sheet, firstRow: currentRow, lastRow: currentRow, firstCol, lastCol };
132
+ }
133
+ case '#Data':
134
+ default: {
135
+ const firstRow = table.headerRow + 1;
136
+ // An empty table has no data rows. Returning the header row instead
137
+ // would make =SUM(Orders[Amount]) add the word "Amount".
138
+ if (firstRow > table.lastRow)
139
+ return null;
140
+ return { sheet: table.sheet, firstRow, lastRow: table.lastRow, firstCol, lastCol };
141
+ }
142
+ }
143
+ }
144
+ /** How many data rows a table holds. */
145
+ export function rowCountOf(table) {
146
+ return Math.max(0, table.lastRow - table.headerRow);
147
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Formula lexer.
3
+ *
4
+ * The awkward part of spreadsheet lexing is that `A1`, `A1:B2`, `Sheet1!A1`,
5
+ * `'Price list'!A1:B2`, `SUM(` and `TRUE` all start life as the same run of
6
+ * letters and digits. Rather than guess, the lexer reads the whole word plus
7
+ * whatever qualifies it (a `!` before, a `:` after, a `(` after) and decides
8
+ * once, with all of it in hand.
9
+ *
10
+ * `$` is part of a word here and stays in the text handed to `parseA1`, which
11
+ * is what keeps absolute references absolute.
12
+ */
13
+ import { type CellRef } from './address.js';
14
+ import { type BinaryOp } from './ast.js';
15
+ export type TableRefToken = {
16
+ t: 'table';
17
+ table: string | null;
18
+ column: string | null;
19
+ columnTo?: string | null;
20
+ specifier: '#All' | '#Data' | '#Headers' | '#Totals' | '#ThisRow';
21
+ };
22
+ export type Token = TableRefToken | {
23
+ t: 'num';
24
+ v: number;
25
+ } | {
26
+ t: 'str';
27
+ v: string;
28
+ } | {
29
+ t: 'bool';
30
+ v: boolean;
31
+ } | {
32
+ t: 'ref';
33
+ ref: CellRef;
34
+ } | {
35
+ t: 'range';
36
+ from: CellRef;
37
+ to: CellRef;
38
+ } | {
39
+ t: 'name';
40
+ v: string;
41
+ } | {
42
+ t: 'fn';
43
+ v: string;
44
+ } | {
45
+ t: 'op';
46
+ v: BinaryOp;
47
+ } | {
48
+ t: 'lparen';
49
+ } | {
50
+ t: 'rparen';
51
+ } | {
52
+ t: 'comma';
53
+ };
54
+ export declare function tokenize(src: string): Token[];