@shbernal/ts-xlsx 3.1.0 → 3.2.0

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 (214) hide show
  1. package/README.md +30 -23
  2. package/dist/bytes.d.ts +3 -2
  3. package/dist/core/address.d.ts +36 -0
  4. package/dist/core/address.js +47 -9
  5. package/dist/core/array-formula-ranges.d.ts +33 -0
  6. package/dist/core/array-formula-ranges.js +62 -0
  7. package/dist/core/color-resolution.js +2 -2
  8. package/dist/core/column.js +3 -2
  9. package/dist/core/comment-thread.d.ts +7 -3
  10. package/dist/core/conditional-formatting-overlay.d.ts +9 -0
  11. package/dist/core/conditional-formatting-overlay.js +28 -1
  12. package/dist/core/conditional-formatting.d.ts +98 -10
  13. package/dist/core/conditional-formatting.js +42 -1
  14. package/dist/core/data-validation-overlay.d.ts +5 -0
  15. package/dist/core/data-validation-overlay.js +15 -0
  16. package/dist/core/data-validation.d.ts +16 -0
  17. package/dist/core/data-validation.js +15 -0
  18. package/dist/core/date-format.js +14 -4
  19. package/dist/core/date.js +1 -1
  20. package/dist/core/formula-references.d.ts +26 -0
  21. package/dist/core/formula-references.js +173 -0
  22. package/dist/core/formula-scan.d.ts +2 -0
  23. package/dist/core/formula-scan.js +58 -0
  24. package/dist/core/formula.d.ts +51 -18
  25. package/dist/core/formula.js +77 -86
  26. package/dist/core/function-values.d.ts +4 -0
  27. package/dist/core/function-values.js +58 -0
  28. package/dist/core/future-functions.d.ts +3 -0
  29. package/dist/core/{modern-functions.js → future-functions.js} +119 -88
  30. package/dist/core/grid-edits.d.ts +28 -0
  31. package/dist/core/grid-edits.js +193 -25
  32. package/dist/core/hyperlink.d.ts +39 -0
  33. package/dist/core/hyperlink.js +88 -0
  34. package/dist/core/image.d.ts +36 -5
  35. package/dist/core/image.js +9 -0
  36. package/dist/core/internal.d.ts +3 -1
  37. package/dist/core/limits.d.ts +5 -3
  38. package/dist/core/merge.d.ts +13 -0
  39. package/dist/core/merge.js +15 -1
  40. package/dist/core/page-setup.d.ts +26 -11
  41. package/dist/core/pivot-table.d.ts +49 -10
  42. package/dist/core/pivot-table.js +55 -21
  43. package/dist/core/protection.d.ts +66 -4
  44. package/dist/core/protection.js +4 -0
  45. package/dist/core/row.js +3 -2
  46. package/dist/core/structured-reference.d.ts +7 -0
  47. package/dist/core/structured-reference.js +7 -0
  48. package/dist/core/table.d.ts +38 -8
  49. package/dist/core/table.js +37 -7
  50. package/dist/core/theme.d.ts +2 -0
  51. package/dist/core/theme.js +4 -1
  52. package/dist/core/value.d.ts +51 -28
  53. package/dist/core/value.js +15 -12
  54. package/dist/core/workbook-styles.d.ts +17 -1
  55. package/dist/core/workbook-styles.js +4 -0
  56. package/dist/core/workbook-theme.js +5 -3
  57. package/dist/core/workbook.d.ts +20 -3
  58. package/dist/core/workbook.js +26 -5
  59. package/dist/core/worksheet-merges.d.ts +9 -4
  60. package/dist/core/worksheet-merges.js +17 -11
  61. package/dist/core/worksheet-model.d.ts +1 -1
  62. package/dist/core/worksheet-model.js +4 -0
  63. package/dist/core/worksheet-pictures.d.ts +3 -3
  64. package/dist/core/worksheet-pictures.js +7 -6
  65. package/dist/core/worksheet.d.ts +110 -21
  66. package/dist/core/worksheet.js +174 -49
  67. package/dist/entries/core.d.ts +7 -5
  68. package/dist/entries/core.js +4 -3
  69. package/dist/entries/errors.d.ts +1 -0
  70. package/dist/entries/errors.js +1 -0
  71. package/dist/errors.js +1 -1
  72. package/dist/io/cell-metadata/metadata.d.ts +42 -0
  73. package/dist/io/cell-metadata/metadata.js +35 -0
  74. package/dist/io/cell-metadata/rich-values.d.ts +11 -0
  75. package/dist/io/cell-metadata/rich-values.js +80 -0
  76. package/dist/io/csv/delimiter.d.ts +14 -2
  77. package/dist/io/csv/delimiter.js +12 -0
  78. package/dist/io/csv/errors.d.ts +14 -0
  79. package/dist/io/csv/errors.js +5 -0
  80. package/dist/io/csv/read.d.ts +6 -1
  81. package/dist/io/csv/read.js +46 -24
  82. package/dist/io/csv/write.d.ts +10 -3
  83. package/dist/io/csv/write.js +16 -7
  84. package/dist/io/opc/part-paths.js +4 -4
  85. package/dist/io/opc/read-opc.d.ts +4 -0
  86. package/dist/io/opc/read-opc.js +10 -4
  87. package/dist/io/opc/strict-relationships.d.ts +2 -0
  88. package/dist/io/opc/strict-relationships.js +12 -0
  89. package/dist/io/read-policy/column-budget.d.ts +26 -0
  90. package/dist/io/{xlsx → read-policy}/column-budget.js +4 -8
  91. package/dist/io/read-policy/read-repair.d.ts +74 -0
  92. package/dist/io/read-policy/read-repair.js +119 -0
  93. package/dist/io/style/cell-style-resolution.d.ts +25 -0
  94. package/dist/io/style/cell-style-resolution.js +30 -0
  95. package/dist/io/xlsb/formula.js +4 -2
  96. package/dist/io/xlsb/ptg-functions.js +110 -0
  97. package/dist/io/xlsb/read-metadata.d.ts +7 -0
  98. package/dist/io/xlsb/read-metadata.js +69 -0
  99. package/dist/io/xlsb/read-worksheet.d.ts +6 -0
  100. package/dist/io/xlsb/read-worksheet.js +95 -37
  101. package/dist/io/xlsb/read.js +26 -8
  102. package/dist/io/xlsb/record-types.d.ts +13 -0
  103. package/dist/io/xlsb/record-types.js +13 -0
  104. package/dist/io/xlsb/sheet-protection.d.ts +20 -0
  105. package/dist/io/xlsb/sheet-protection.js +60 -0
  106. package/dist/io/xlsx/cell-accumulator.d.ts +20 -12
  107. package/dist/io/xlsx/cell-accumulator.js +49 -19
  108. package/dist/io/xlsx/cell-metadata.d.ts +39 -0
  109. package/dist/io/xlsx/cell-metadata.js +162 -0
  110. package/dist/io/xlsx/cell-value.d.ts +16 -4
  111. package/dist/io/xlsx/cell-value.js +38 -21
  112. package/dist/io/xlsx/color-xml.js +1 -1
  113. package/dist/io/xlsx/column-span.d.ts +11 -0
  114. package/dist/io/xlsx/column-span.js +9 -0
  115. package/dist/io/xlsx/comments.d.ts +3 -28
  116. package/dist/io/xlsx/comments.js +3 -65
  117. package/dist/io/xlsx/conditional-formatting.d.ts +42 -25
  118. package/dist/io/xlsx/conditional-formatting.js +424 -129
  119. package/dist/io/xlsx/data-validation.d.ts +12 -7
  120. package/dist/io/xlsx/data-validation.js +41 -29
  121. package/dist/io/xlsx/edit-vba.js +43 -36
  122. package/dist/io/xlsx/hyperlinks.d.ts +16 -50
  123. package/dist/io/xlsx/hyperlinks.js +8 -37
  124. package/dist/io/xlsx/images.d.ts +49 -16
  125. package/dist/io/xlsx/images.js +145 -44
  126. package/dist/io/xlsx/namespaces.d.ts +8 -0
  127. package/dist/io/xlsx/namespaces.js +4 -0
  128. package/dist/io/xlsx/package-plan.d.ts +48 -15
  129. package/dist/io/xlsx/package-plan.js +46 -9
  130. package/dist/io/xlsx/part-names.d.ts +6 -8
  131. package/dist/io/xlsx/part-names.js +3 -7
  132. package/dist/io/xlsx/pivot.d.ts +5 -5
  133. package/dist/io/xlsx/pivot.js +23 -22
  134. package/dist/io/xlsx/preserved-splices.d.ts +6 -0
  135. package/dist/io/xlsx/preserved-splices.js +74 -0
  136. package/dist/io/xlsx/read-comments.d.ts +29 -0
  137. package/dist/io/xlsx/read-comments.js +75 -0
  138. package/dist/io/xlsx/read-parts.d.ts +2 -2
  139. package/dist/io/xlsx/read-parts.js +44 -37
  140. package/dist/io/xlsx/read-pivot.js +9 -3
  141. package/dist/io/xlsx/read-rich-runs.d.ts +8 -5
  142. package/dist/io/xlsx/read-rich-runs.js +18 -1
  143. package/dist/io/xlsx/read-rows.d.ts +18 -7
  144. package/dist/io/xlsx/read-rows.js +47 -37
  145. package/dist/io/xlsx/read-styles.d.ts +0 -25
  146. package/dist/io/xlsx/read-styles.js +39 -34
  147. package/dist/io/xlsx/read-workbook-xml.d.ts +6 -0
  148. package/dist/io/xlsx/read-workbook-xml.js +9 -4
  149. package/dist/io/xlsx/read-worksheet.d.ts +8 -3
  150. package/dist/io/xlsx/read-worksheet.js +45 -23
  151. package/dist/io/xlsx/read.d.ts +0 -4
  152. package/dist/io/xlsx/read.js +17 -21
  153. package/dist/io/xlsx/relationships.d.ts +3 -0
  154. package/dist/io/xlsx/relationships.js +4 -0
  155. package/dist/io/xlsx/row-position.d.ts +3 -1
  156. package/dist/io/xlsx/row-xml.d.ts +18 -7
  157. package/dist/io/xlsx/row-xml.js +54 -35
  158. package/dist/io/xlsx/sheet-properties.js +68 -42
  159. package/dist/io/xlsx/strict-parts.d.ts +7 -0
  160. package/dist/io/xlsx/strict-parts.js +171 -0
  161. package/dist/io/xlsx/style-elements.d.ts +0 -2
  162. package/dist/io/xlsx/style-elements.js +9 -29
  163. package/dist/io/xlsx/styles.d.ts +5 -0
  164. package/dist/io/xlsx/styles.js +11 -5
  165. package/dist/io/xlsx/tables.d.ts +10 -2
  166. package/dist/io/xlsx/tables.js +49 -42
  167. package/dist/io/xlsx/theme-xml.d.ts +6 -0
  168. package/dist/io/xlsx/theme-xml.js +16 -19
  169. package/dist/io/xlsx/threaded-comments.d.ts +6 -1
  170. package/dist/io/xlsx/threaded-comments.js +14 -7
  171. package/dist/io/xlsx/workbook-xml.d.ts +13 -24
  172. package/dist/io/xlsx/workbook-xml.js +15 -33
  173. package/dist/io/xlsx/worksheet-xml.d.ts +5 -2
  174. package/dist/io/xlsx/worksheet-xml.js +37 -69
  175. package/dist/io/xlsx/write-stream.d.ts +28 -6
  176. package/dist/io/xlsx/write-stream.js +42 -17
  177. package/dist/io/xlsx/write.d.ts +11 -4
  178. package/dist/io/xlsx/write.js +153 -90
  179. package/dist/rel-type.d.ts +9 -0
  180. package/dist/rel-type.js +3 -0
  181. package/dist/vba/bytes.d.ts +2 -0
  182. package/dist/vba/bytes.js +11 -0
  183. package/dist/vba/cfb-format.d.ts +47 -0
  184. package/dist/vba/cfb-format.js +34 -0
  185. package/dist/vba/cfb-writer.d.ts +1 -2
  186. package/dist/vba/cfb-writer.js +33 -36
  187. package/dist/vba/cfb.d.ts +9 -4
  188. package/dist/vba/cfb.js +55 -43
  189. package/dist/vba/dir-records.d.ts +6 -0
  190. package/dist/vba/dir-records.js +10 -2
  191. package/dist/vba/errors.d.ts +4 -4
  192. package/dist/vba/index.d.ts +0 -2
  193. package/dist/vba/index.js +0 -2
  194. package/dist/vba/ms-ovba.d.ts +6 -2
  195. package/dist/vba/ms-ovba.js +15 -6
  196. package/dist/vba/project-editor.js +102 -87
  197. package/dist/vba/project.d.ts +4 -0
  198. package/dist/vba/project.js +12 -6
  199. package/dist/vba/vba-encoding.d.ts +4 -10
  200. package/dist/vba/vba-encoding.js +2 -15
  201. package/dist/xml/xml-read.d.ts +29 -1
  202. package/dist/xml/xml-read.js +56 -0
  203. package/dist/xml/xml-scan.d.ts +13 -0
  204. package/dist/xml/xml-scan.js +58 -7
  205. package/dist/xml/xml.d.ts +33 -1
  206. package/dist/xml/xml.js +22 -0
  207. package/package.json +5 -4
  208. package/skills/ts-xlsx-upstream/SKILL.md +21 -13
  209. package/dist/core/modern-functions.d.ts +0 -1
  210. package/dist/io/xlsx/cell-style-resolution.d.ts +0 -25
  211. package/dist/io/xlsx/cell-style-resolution.js +0 -29
  212. package/dist/io/xlsx/column-budget.d.ts +0 -26
  213. package/dist/io/xlsx/read-repair.d.ts +0 -26
  214. package/dist/io/xlsx/read-repair.js +0 -39
@@ -2,7 +2,11 @@ import { type AutoFilter } from './autofilter.ts';
2
2
  import { Cell } from './cell.ts';
3
3
  import type { ConditionalFormattingOverlay } from './conditional-formatting-overlay.ts';
4
4
  import type { DataValidationOverlay } from './data-validation-overlay.ts';
5
+ import { type SheetSplice } from './formula-references.ts';
6
+ import type { HyperlinkOverlay } from './hyperlink.ts';
5
7
  import { type AnchoredImage } from './image.ts';
8
+ import type { PageBreak } from './page-setup.ts';
9
+ import { type ParsedPivotTable, type PivotTable } from './pivot-table.ts';
6
10
  import type { Table } from './table.ts';
7
11
  import { type CellValue } from './value.ts';
8
12
  import type { WorksheetComments } from './worksheet-comments.ts';
@@ -22,10 +26,22 @@ interface GridStorage {
22
26
  readonly columns: Map<number, ColumnProperties>;
23
27
  readonly merges: WorksheetMerges;
24
28
  readonly tables: Table[];
29
+ readonly pivotTables: readonly PivotTable[];
30
+ readonly loadedPivotTables: ParsedPivotTable[];
25
31
  readonly images: AnchoredImage[];
26
32
  readonly dataValidations: DataValidationOverlay;
27
33
  readonly conditionalFormattings: ConditionalFormattingOverlay;
34
+ readonly hyperlinks: HyperlinkOverlay;
35
+ /** The sheet's name, which an unqualified reference in one of its formulas means. */
36
+ readonly sheetName: () => string;
37
+ /**
38
+ * What moves the formulas outside this sheet that name it: every other sheet's, and the workbook's
39
+ * defined names. `undefined` for a sheet no workbook holds.
40
+ */
41
+ readonly formulaHost: () => ((edit: SheetSplice) => void) | undefined;
28
42
  readonly comments: WorksheetComments;
43
+ readonly rowBreaks: PageBreak[];
44
+ readonly columnBreaks: PageBreak[];
29
45
  readonly autoFilter: AutoFilterSlot;
30
46
  }
31
47
  export declare class GridEdits {
@@ -33,5 +49,17 @@ export declare class GridEdits {
33
49
  constructor(storage: GridStorage);
34
50
  spliceRows(start: number, count: number, inserted: Map<number, Cell>[]): void;
35
51
  spliceColumns(start: number, count: number, inserts: CellValue[][]): void;
52
+ /**
53
+ * Move the references this sheet's formulas make to the sheet a splice changed, which may be this
54
+ * sheet or another: every cell formula, and every data validation's and conditional format's formula.
55
+ *
56
+ * A shared formula is described once, by its master, and each clone is that text translated to where
57
+ * the clone sits. A splice usually keeps that true, since the master, the clone and what they refer to
58
+ * all move together, but not always: an insert between the cells one clone refers to and the cells its
59
+ * master does grows one reference and not the other, and a delete can take the master and leave the
60
+ * clone. A clone the moved master no longer describes stops sharing and becomes a formula of its own,
61
+ * spelled as it was rewritten; the rest of the group keeps sharing.
62
+ */
63
+ spliceFormulas(edit: SheetSplice): void;
36
64
  }
37
65
  export {};
@@ -1,36 +1,54 @@
1
- import { boundedRect, decodeRange, encodeAddress, encodeRect, tryDecodeCellRef } from './address.js';
1
+ import { boundedRect, decodeRange, encodeAddress, encodeRange, encodeRect, tryDecodeCellRef, tryDecodeRange, } from './address.js';
2
2
  import { shiftAutoFilter } from './autofilter.js';
3
3
  import { Cell, copyCellContent } from './cell.js';
4
4
  import { replaceContents } from './containers.js';
5
- import { isDeletedSpan, shiftIndex, shiftRect } from './grid-shift.js';
5
+ import { spliceFormula, translateFormula } from './formula-references.js';
6
+ import { isDeletedSpan, shiftIndex, shiftPoint, shiftRect } from './grid-shift.js';
6
7
  import { isOneCellAnchor } from './image.js';
8
+ import { INTERNAL } from './internal.js';
9
+ import { splicePivotSource } from './pivot-table.js';
7
10
  import { positionalPlacements } from './row-input.js';
8
- import { isSharedFormulaValue } from './value.js';
11
+ import { isArrayFormulaValue, isDataTableFormulaValue, isFormulaValue, isSharedFormulaValue, } from './value.js';
9
12
  export class GridEdits {
10
13
  #rows;
11
14
  #rowProperties;
12
15
  #columns;
13
16
  #merges;
14
17
  #tables;
18
+ #pivotTables;
19
+ #loadedPivotTables;
15
20
  #images;
16
21
  #dataValidations;
17
22
  #conditionalFormattings;
23
+ #hyperlinks;
18
24
  #comments;
25
+ #rowBreaks;
26
+ #columnBreaks;
19
27
  #autoFilter;
28
+ #sheetName;
29
+ #formulaHost;
20
30
  constructor(storage) {
21
31
  this.#rows = storage.rows;
22
32
  this.#rowProperties = storage.rowProperties;
23
33
  this.#columns = storage.columns;
24
34
  this.#merges = storage.merges;
25
35
  this.#tables = storage.tables;
36
+ this.#pivotTables = storage.pivotTables;
37
+ this.#loadedPivotTables = storage.loadedPivotTables;
26
38
  this.#images = storage.images;
27
39
  this.#dataValidations = storage.dataValidations;
28
40
  this.#conditionalFormattings = storage.conditionalFormattings;
41
+ this.#hyperlinks = storage.hyperlinks;
29
42
  this.#comments = storage.comments;
43
+ this.#rowBreaks = storage.rowBreaks;
44
+ this.#columnBreaks = storage.columnBreaks;
30
45
  this.#autoFilter = storage.autoFilter;
46
+ this.#sheetName = storage.sheetName;
47
+ this.#formulaHost = storage.formulaHost;
31
48
  }
32
49
  spliceRows(start, count, inserted) {
33
50
  const splice = { axis: 'row', start, count, delta: inserted.length - count };
51
+ this.#moveFormulaReferences(splice);
34
52
  const shifted = new Map();
35
53
  for (const [row, cols] of this.#rows) {
36
54
  if (row < start)
@@ -46,15 +64,11 @@ export class GridEdits {
46
64
  this.#rows.clear();
47
65
  for (const [row, cols] of shifted)
48
66
  this.#rows.set(row, cols);
49
- this.#shiftLineProperties(this.#rowProperties, splice);
50
- this.#shiftMerges(splice);
51
- this.#shiftTables(splice);
52
- this.#shiftImages(splice);
53
- this.#reanchorSharedFormulas(splice);
54
- this.#shiftRangeBoundOverlays(splice);
67
+ this.#shiftAnchored(splice, this.#rowProperties);
55
68
  }
56
69
  spliceColumns(start, count, inserts) {
57
70
  const splice = { axis: 'col', start, count, delta: inserts.length - count };
71
+ this.#moveFormulaReferences(splice);
58
72
  const shiftedRows = new Map();
59
73
  for (const [row, cols] of this.#rows) {
60
74
  const shifted = new Map();
@@ -85,16 +99,114 @@ export class GridEdits {
85
99
  });
86
100
  for (const [row, cols] of shiftedRows)
87
101
  this.#rows.set(row, cols);
88
- this.#shiftLineProperties(this.#columns, splice);
102
+ this.#shiftAnchored(splice, this.#columns);
103
+ }
104
+ #moveFormulaReferences(splice) {
105
+ const edit = { sheet: this.#sheetName(), splice };
106
+ this.spliceFormulas(edit);
107
+ this.#formulaHost()?.(edit);
108
+ }
109
+ spliceFormulas(edit) {
110
+ const home = this.#sheetName();
111
+ const { splice } = edit;
112
+ const rewrite = (formula) => spliceFormula(formula, home, edit);
113
+ const cellsMove = home.toLowerCase() === edit.sheet.toLowerCase();
114
+ const after = (cell) => {
115
+ if (!cellsMove)
116
+ return cell;
117
+ return splice.axis === 'row'
118
+ ? { col: cell.col, row: shiftIndex(cell.row, splice) }
119
+ : { col: shiftIndex(cell.col, splice), row: cell.row };
120
+ };
121
+ const taken = (cell) => {
122
+ const line = splice.axis === 'row' ? cell.row : cell.col;
123
+ return cellsMove && isDeletedSpan(line, line, splice);
124
+ };
125
+ const masters = new Map();
126
+ for (const cols of this.#rows.values()) {
127
+ for (const cell of cols.values()) {
128
+ const value = cell.value;
129
+ if (isArrayFormulaValue(value)) {
130
+ const rewritten = rewrite(value.formula);
131
+ if (rewritten !== value.formula)
132
+ cell.value = { ...value, formula: rewritten };
133
+ continue;
134
+ }
135
+ if (!isFormulaValue(value))
136
+ continue;
137
+ masters.set(encodeAddress(cell.col, cell.row), {
138
+ cell,
139
+ formula: value.formula,
140
+ rewritten: rewrite(value.formula),
141
+ });
142
+ }
143
+ }
144
+ for (const cols of this.#rows.values()) {
145
+ for (const cell of cols.values()) {
146
+ const value = cell.value;
147
+ if (!isSharedFormulaValue(value))
148
+ continue;
149
+ const master = masters.get(value.sharedFormula);
150
+ if (master === undefined) {
151
+ if (value.formula === undefined)
152
+ continue;
153
+ const rewritten = rewrite(value.formula);
154
+ if (rewritten !== value.formula)
155
+ cell.value = { ...value, formula: rewritten };
156
+ continue;
157
+ }
158
+ const source = value.formula ??
159
+ translateFormula(master.formula, cell.col - master.cell.col, cell.row - master.cell.row);
160
+ const rewritten = rewrite(source);
161
+ const from = after(master.cell);
162
+ const to = after(cell);
163
+ const described = !taken(master.cell) &&
164
+ translateFormula(master.rewritten, to.col - from.col, to.row - from.row) === rewritten;
165
+ if (described) {
166
+ if (value.formula !== undefined && rewritten !== value.formula) {
167
+ cell.value = { ...value, formula: rewritten };
168
+ }
169
+ continue;
170
+ }
171
+ cell.value =
172
+ value.result === undefined
173
+ ? { formula: rewritten }
174
+ : { formula: rewritten, result: value.result };
175
+ }
176
+ }
177
+ for (const { cell, formula, rewritten } of masters.values()) {
178
+ const value = cell.value;
179
+ if (rewritten !== formula && isFormulaValue(value))
180
+ cell.value = { ...value, formula: rewritten };
181
+ }
182
+ this.#dataValidations.mapFormulas(rewrite);
183
+ this.#conditionalFormattings.mapFormulas(rewrite);
184
+ for (const table of this.#tables)
185
+ table[INTERNAL].rewriteFormulas(rewrite);
186
+ for (const pivot of this.#pivotTables)
187
+ pivot[INTERNAL].spliceSource(edit);
188
+ for (const [index, loaded] of this.#loadedPivotTables.entries()) {
189
+ const { source } = loaded;
190
+ if (source.kind !== 'worksheet' || source.inAnotherWorkbook)
191
+ continue;
192
+ const ref = splicePivotSource(source.sheet, source.ref, edit);
193
+ if (ref !== source.ref)
194
+ this.#loadedPivotTables[index] = { ...loaded, source: { ...source, ref } };
195
+ }
196
+ }
197
+ #shiftAnchored(splice, lineProperties) {
198
+ this.#shiftLineProperties(lineProperties, splice);
199
+ this.#shiftPageBreaks(splice);
89
200
  this.#shiftMerges(splice);
90
201
  this.#shiftTables(splice);
91
202
  this.#shiftImages(splice);
92
- this.#reanchorSharedFormulas(splice);
203
+ this.#reanchorValueReferences(splice);
93
204
  this.#shiftRangeBoundOverlays(splice);
94
205
  }
95
206
  #shiftRangeBoundOverlays(splice) {
96
207
  this.#dataValidations.shift(splice);
97
208
  this.#conditionalFormattings.shift(splice);
209
+ this.#hyperlinks.shift(splice);
98
210
  this.#comments.shift(splice);
99
211
  const filter = this.#autoFilter.get();
100
212
  if (filter !== undefined)
@@ -114,22 +226,13 @@ export class GridEdits {
114
226
  }
115
227
  return moved;
116
228
  }
117
- #reanchorSharedFormulas(splice) {
229
+ #reanchorValueReferences(splice) {
118
230
  for (const cols of this.#rows.values()) {
119
231
  for (const cell of cols.values()) {
120
232
  const value = cell.value;
121
- if (!isSharedFormulaValue(value))
122
- continue;
123
- const master = tryDecodeCellRef(value.sharedFormula);
124
- if (master === undefined)
125
- continue;
126
- const anchored = splice.axis === 'row'
127
- ? encodeAddress(master.col, shiftIndex(master.row, splice))
128
- : encodeAddress(shiftIndex(master.col, splice), master.row);
129
- if (anchored === value.sharedFormula)
130
- continue;
131
- const reanchored = { ...value, sharedFormula: anchored };
132
- cell.value = reanchored;
233
+ const moved = reanchoredValue(value, splice);
234
+ if (moved !== value)
235
+ cell.value = moved;
133
236
  }
134
237
  }
135
238
  }
@@ -144,6 +247,17 @@ export class GridEdits {
144
247
  for (const [index, value] of shifted)
145
248
  map.set(index, value);
146
249
  }
250
+ #shiftPageBreaks(splice) {
251
+ const breaks = splice.axis === 'row' ? this.#rowBreaks : this.#columnBreaks;
252
+ const moved = breaks.flatMap((brk) => {
253
+ const line = brk.id + 1;
254
+ if (isDeletedSpan(line, line, splice))
255
+ return [];
256
+ const id = shiftIndex(line, splice) - 1;
257
+ return [id === brk.id ? brk : { ...brk, id }];
258
+ });
259
+ replaceContents(breaks, moved);
260
+ }
147
261
  #shiftMerges(splice) {
148
262
  const merges = [];
149
263
  const rects = [];
@@ -178,8 +292,62 @@ export class GridEdits {
178
292
  const anchor = isOneCellAnchor(image.anchor)
179
293
  ? { ...image.anchor, from }
180
294
  : { ...image.anchor, from, to: shiftAnchor(image.anchor.to) };
181
- return { imageId: image.imageId, anchor };
295
+ return { ...image, anchor };
182
296
  });
183
297
  replaceContents(this.#images, moved);
184
298
  }
185
299
  }
300
+ function reanchoredValue(value, splice) {
301
+ if (isSharedFormulaValue(value)) {
302
+ const master = tryDecodeCellRef(value.sharedFormula);
303
+ if (master === undefined)
304
+ return value;
305
+ const anchored = splice.axis === 'row'
306
+ ? encodeAddress(master.col, shiftIndex(master.row, splice))
307
+ : encodeAddress(shiftIndex(master.col, splice), master.row);
308
+ return anchored === value.sharedFormula ? value : { ...value, sharedFormula: anchored };
309
+ }
310
+ if (isArrayFormulaValue(value)) {
311
+ const ref = shiftedRange(value.ref, splice) ?? value.ref;
312
+ return ref === value.ref ? value : { ...value, ref };
313
+ }
314
+ if (isDataTableFormulaValue(value)) {
315
+ const ref = shiftedRange(value.ref, splice) ?? value.ref;
316
+ const r1 = inputAfter(value.r1, value.r1Deleted, splice);
317
+ const r2 = inputAfter(value.r2, value.r2Deleted, splice);
318
+ if (ref === value.ref && r1.same && r2.same)
319
+ return value;
320
+ return {
321
+ ...value,
322
+ ref,
323
+ ...(r1.ref === undefined ? {} : { r1: r1.ref }),
324
+ ...(r2.ref === undefined ? {} : { r2: r2.ref }),
325
+ ...(r1.deleted ? { r1Deleted: true } : {}),
326
+ ...(r2.deleted ? { r2Deleted: true } : {}),
327
+ };
328
+ }
329
+ return value;
330
+ }
331
+ function shiftedRange(ref, splice) {
332
+ const decoded = tryDecodeRange(ref);
333
+ const rect = decoded === undefined ? undefined : boundedRect(decoded);
334
+ if (rect === undefined)
335
+ return ref;
336
+ const moved = shiftRect(rect, splice);
337
+ if (moved === undefined)
338
+ return undefined;
339
+ return sameRect(moved, rect) ? ref : encodeRange(moved);
340
+ }
341
+ function inputAfter(ref, deleted, splice) {
342
+ const cell = ref === undefined || deleted === true ? undefined : tryDecodeCellRef(ref);
343
+ if (cell === undefined)
344
+ return { ref, deleted: deleted === true, same: true };
345
+ const moved = shiftPoint(cell, splice);
346
+ if (moved === undefined)
347
+ return { ref, deleted: true, same: false };
348
+ const same = moved.row === cell.row && moved.col === cell.col;
349
+ return { ref: same ? ref : encodeAddress(moved.col, moved.row), deleted: false, same };
350
+ }
351
+ function sameRect(a, b) {
352
+ return a.top === b.top && a.left === b.left && a.bottom === b.bottom && a.right === b.right;
353
+ }
@@ -0,0 +1,39 @@
1
+ import { type AxisSplice } from './grid-shift.ts';
2
+ /** A hyperlink and the cells it covers. */
3
+ export interface Hyperlink {
4
+ /** The cells the link covers: one cell (`'B2'`) or a rectangle of cells (`'D1:H1'`). */
5
+ readonly ref: string;
6
+ /**
7
+ * Where the link goes: a URL or a path, or a `#`-prefixed place in this workbook such as
8
+ * `#Summary!A1` or `#TaxRate`.
9
+ */
10
+ readonly target: string;
11
+ /** The text shown when the pointer rests on the link. */
12
+ readonly tooltip?: string;
13
+ }
14
+ /** The links on one sheet, in the order they were added, each with the rectangle it covers. */
15
+ export declare class HyperlinkOverlay {
16
+ #private;
17
+ /**
18
+ * Add a link, replacing one over the same cells. The `ref` is stored in its canonical spelling.
19
+ *
20
+ * @throws {SyntaxError} if `ref` is not a reference.
21
+ * @throws {AuthoringError} if `ref` names a whole row or column rather than cells.
22
+ */
23
+ add(link: Hyperlink): void;
24
+ /** Remove the link whose `ref` names exactly these cells, and report whether there was one. */
25
+ remove(ref: string): boolean;
26
+ /** The links on the sheet, in the order they were added. */
27
+ get entries(): readonly Hyperlink[];
28
+ /** The link covering the 1-based `col`/`row`, the last added of any that do, or `undefined`. */
29
+ at(col: number, row: number): Hyperlink | undefined;
30
+ /**
31
+ * Move every link through a row or column splice. A link grows when lines are inserted inside it and
32
+ * shrinks when some of its lines are deleted, and one the delete takes whole goes with it.
33
+ */
34
+ shift(splice: AxisSplice): void;
35
+ /** Give row `to` a copy of every link that lies wholly within row `from`, as a row copy does. */
36
+ copyRow(from: number, to: number): void;
37
+ /** Drop every link, leaving the overlay empty. */
38
+ clear(): void;
39
+ }
@@ -0,0 +1,88 @@
1
+ import { AuthoringError, quoted } from '../errors.js';
2
+ import { boundedRect, decodeRange, encodeAddress, encodeRect, tryDecodeRange, } from './address.js';
3
+ import { replaceContents } from './containers.js';
4
+ import { shiftRect } from './grid-shift.js';
5
+ export class HyperlinkOverlay {
6
+ #entries = [];
7
+ #rects = [];
8
+ add(link) {
9
+ const rect = boundedRect(decodeRange(link.ref));
10
+ if (rect === undefined) {
11
+ throw new AuthoringError(`hyperlink range ${quoted(link.ref)} names a whole row or column, not cells`);
12
+ }
13
+ const stored = storedLink(link, refOf(rect));
14
+ this.#removeRef(stored.ref);
15
+ this.#entries.push(stored);
16
+ this.#rects.push(rect);
17
+ }
18
+ remove(ref) {
19
+ const decoded = tryDecodeRange(ref);
20
+ const rect = decoded === undefined ? undefined : boundedRect(decoded);
21
+ return rect !== undefined && this.#removeRef(refOf(rect));
22
+ }
23
+ get entries() {
24
+ return this.#entries;
25
+ }
26
+ at(col, row) {
27
+ for (let index = this.#entries.length - 1; index >= 0; index--) {
28
+ const rect = this.#rects[index];
29
+ if (rect !== undefined &&
30
+ col >= rect.left &&
31
+ col <= rect.right &&
32
+ row >= rect.top &&
33
+ row <= rect.bottom) {
34
+ return this.#entries[index];
35
+ }
36
+ }
37
+ return undefined;
38
+ }
39
+ shift(splice) {
40
+ const entries = [];
41
+ const rects = [];
42
+ this.#entries.forEach((entry, index) => {
43
+ const rect = this.#rects[index];
44
+ const moved = rect === undefined ? undefined : shiftRect(rect, splice);
45
+ if (moved === undefined)
46
+ return;
47
+ const ref = refOf(moved);
48
+ entries.push(ref === entry.ref ? entry : storedLink(entry, ref));
49
+ rects.push(moved);
50
+ });
51
+ replaceContents(this.#entries, entries);
52
+ replaceContents(this.#rects, rects);
53
+ }
54
+ copyRow(from, to) {
55
+ const copies = this.#entries.flatMap((entry, index) => {
56
+ const rect = this.#rects[index];
57
+ return rect === undefined || rect.top !== from || rect.bottom !== from
58
+ ? []
59
+ : [storedLink(entry, refOf({ ...rect, top: to, bottom: to }))];
60
+ });
61
+ for (const copy of copies)
62
+ this.add(copy);
63
+ }
64
+ clear() {
65
+ this.#entries.length = 0;
66
+ this.#rects.length = 0;
67
+ }
68
+ #removeRef(ref) {
69
+ const index = this.#entries.findIndex((entry) => entry.ref === ref);
70
+ if (index === -1)
71
+ return false;
72
+ this.#entries.splice(index, 1);
73
+ this.#rects.splice(index, 1);
74
+ return true;
75
+ }
76
+ }
77
+ function refOf(rect) {
78
+ return rect.top === rect.bottom && rect.left === rect.right
79
+ ? encodeAddress(rect.left, rect.top)
80
+ : encodeRect(rect);
81
+ }
82
+ function storedLink(link, ref) {
83
+ return {
84
+ ref,
85
+ target: link.target,
86
+ ...(link.tooltip === undefined ? {} : { tooltip: link.tooltip }),
87
+ };
88
+ }
@@ -14,8 +14,9 @@ export interface AnchorPoint {
14
14
  * DPI-independent by construction: a pixel extent is a fixed physical size regardless of screen. */
15
15
  export declare const PX_TO_EMU = 9525;
16
16
  /** How a two-cell-anchored image tracks edits to the cells it spans. `twoCell` moves and resizes with
17
- * them; `oneCell` moves but keeps its size; `absolute` is pinned to the page and does neither. Excel
18
- * defaults to `oneCell` when the attribute is omitted. */
17
+ * them; `oneCell` moves but keeps its size; `absolute` is pinned to the page and does neither. The
18
+ * schema default, which a file omitting the attribute means, is `twoCell`; an image authored here
19
+ * without one is written as `oneCell`, which is this library's own default. */
19
20
  export type ImageEditAs = 'oneCell' | 'twoCell' | 'absolute';
20
21
  /** Narrow a raw `<xdr:twoCellAnchor editAs>` token to a known {@link ImageEditAs}. */
21
22
  export declare const isImageEditAs: (value: string) => value is ImageEditAs;
@@ -57,12 +58,42 @@ export type RowHeightLookup = (row: number) => number | undefined;
57
58
  * the column's size. An already-integer point keeps a zero offset (unless one was given). The two
58
59
  * lookups supply each column/row's size; a size they leave `undefined` falls back to Excel's default. */
59
60
  export declare function resolveAnchorPoint(point: AnchorPoint, columnWidth: ColumnWidthLookup, rowHeight: RowHeightLookup): AnchorPoint;
60
- /** An image pinned to a worksheet: which workbook media it shows (`imageId`) and where. */
61
- export interface AnchoredImage {
61
+ /**
62
+ * How much of a picture is cut away at each edge, as a fraction of the picture's own size: `0.1` crops
63
+ * a tenth, and a negative value pads the picture out. An absent edge is not cropped.
64
+ */
65
+ export interface ImageCrop {
66
+ readonly left?: number;
67
+ readonly top?: number;
68
+ readonly right?: number;
69
+ readonly bottom?: number;
70
+ }
71
+ /** Where clicking a picture goes: a URL, or a `#`-prefixed place in this workbook (`#Sheet1!C3`), as a
72
+ * sheet hyperlink's `target` spells one. */
73
+ export interface ImageHyperlink {
74
+ readonly target: string;
75
+ /** The text shown when the pointer rests on the picture. */
76
+ readonly tooltip?: string;
77
+ }
78
+ /** What a picture says about itself beyond which image it shows and where. */
79
+ export interface PictureProperties {
80
+ /** Alternative text: what a screen reader says in place of the picture. */
81
+ readonly description?: string;
82
+ /** The picture's title, shown with its alternative text. */
83
+ readonly title?: string;
84
+ readonly crop?: ImageCrop;
85
+ readonly hyperlink?: ImageHyperlink;
86
+ }
87
+ /** An image pinned to a worksheet: which workbook media it shows (`imageId`), where, and the picture's
88
+ * own properties. */
89
+ export interface AnchoredImage extends PictureProperties {
62
90
  /** Index into the workbook's media registry (the id {@link Workbook.addImage} returned). */
63
91
  readonly imageId: number;
64
92
  readonly anchor: ImageAnchor;
65
93
  }
94
+ /** The {@link PictureProperties} a value carries, and nothing else, so an image can be re-pinned or carried
95
+ * to another workbook without its alternative text, crop or link being left behind. */
96
+ export declare function pictureProperties(picture: PictureProperties): PictureProperties;
66
97
  /** A picture's bytes and its file kind, as held in the workbook's media registry. */
67
98
  export interface WorkbookImage {
68
99
  /** Lower-case file extension without a dot: `"png"`, `"jpeg"`, `"gif"`. Drives the media part's
@@ -85,7 +116,7 @@ export declare function normalizeImageExtension(extension: string | undefined, d
85
116
  * itself is what lets an anchor cross that boundary, which is why the transfer form carries bytes
86
117
  * where the stored form carries an id.
87
118
  */
88
- export interface PortableImage {
119
+ export interface PortableImage extends PictureProperties {
89
120
  readonly image: WorkbookImage;
90
121
  readonly anchor: ImageAnchor;
91
122
  }
@@ -19,6 +19,15 @@ export function resolveAnchorPoint(point, columnWidth, rowHeight) {
19
19
  const rowOff = (point.rowOff ?? 0) + Math.round((point.row - row) * rowHeightEmu);
20
20
  return { col, row, colOff, rowOff };
21
21
  }
22
+ export function pictureProperties(picture) {
23
+ const { description, title, crop, hyperlink } = picture;
24
+ return {
25
+ ...(description === undefined ? {} : { description }),
26
+ ...(title === undefined ? {} : { title }),
27
+ ...(crop === undefined ? {} : { crop: { ...crop } }),
28
+ ...(hyperlink === undefined ? {} : { hyperlink: { ...hyperlink } }),
29
+ };
30
+ }
22
31
  const IMAGE_MAGIC = [
23
32
  { ext: 'png', sig: [0x89, 0x50, 0x4e, 0x47] },
24
33
  { ext: 'jpeg', sig: [0xff, 0xd8, 0xff] },
@@ -6,7 +6,9 @@
6
6
  * the same key, on the static side and the instance side respectively, because that class has the
7
7
  * identical problem one layer up. Its constructor took the writer's style registry and its
8
8
  * `flushedSheet()` returned the writer's flushed-row record, so seven internal types were named by a
9
- * published signature and none of them was a type a consumer could write down.
9
+ * published signature and none of them was a type a consumer could write down. `StreamedRow` is
10
+ * constructed through the same static key, because a row built outside its writer could commit, and
11
+ * so evict, a row number that writer never handed out.
10
12
  */
11
13
  export declare const INTERNAL: unique symbol;
12
14
  /**
@@ -46,8 +46,10 @@ export declare const MAX_TABLE_NAME_LENGTH = 255;
46
46
  * Excel's table-name grammar: start with a letter, underscore, or backslash; every later character a
47
47
  * letter, digit, period, or underscore. Unicode letters and digits are allowed.
48
48
  *
49
- * Excel additionally forbids a name that *is* a cell reference (`A1`, `R1C1`), which this pattern
50
- * deliberately does not: the regression corpus treats cell-reference-shaped names like `T1` as valid
51
- * table names, so enforcing that rule would reject a fixture the contract accepts.
49
+ * The pattern is the grammar only. Excel also refuses a name that reads as a reference: an A1 cell on
50
+ * the grid (`T1`), an R1C1 reference (`R`, `C`, `RC`, `R1C1`, `R1X`), or `TRUE`/`FALSE`, in any case.
51
+ * No regular expression states that honestly, because "on the grid" is a bound on the column letters
52
+ * and the row number, so `Worksheet.addTable` checks it separately and a name passing this pattern
53
+ * can still be refused.
52
54
  */
53
55
  export declare const TABLE_NAME_PATTERN: RegExp;
@@ -3,6 +3,19 @@ import type { Cell } from './cell.ts';
3
3
  import { type AxisSplice } from './grid-shift.ts';
4
4
  /** A merged region, as the {@link GridRect} every range-shaped thing in the library is. */
5
5
  export type MergeRect = GridRect;
6
+ /**
7
+ * Each merged range that overlaps a table, paired with the first table it overlaps. Excel forbids a
8
+ * merge inside a table: the writer refuses that geometry and the reader drops the merge, the repair
9
+ * Excel makes on load. Both used to spell the overlap test out by hand. The answer is a list of its
10
+ * own, so the reader can unmerge while it walks it rather than the live `sheet.merges`. An unbounded
11
+ * whole-row/column merge carries no rectangle and overlaps nothing.
12
+ */
13
+ export declare function mergesOverlappingTables<T extends {
14
+ readonly region: GridRect;
15
+ }>(merges: readonly string[], tables: readonly T[]): {
16
+ readonly merge: string;
17
+ readonly table: T;
18
+ }[];
6
19
  /**
7
20
  * Drop any value already sitting in a merge's covered non-anchor cells, keeping only the top-left
8
21
  * anchor, the collapse Excel performs on merge. A leftover covered value would serialise as a
@@ -1,5 +1,19 @@
1
- import { encodeCornerRef, tryDecodeRange } from './address.js';
1
+ import { boundedRect, decodeRange, encodeCornerRef, rectsOverlap, tryDecodeRange, } from './address.js';
2
2
  import { shiftSpan } from './grid-shift.js';
3
+ export function mergesOverlappingTables(merges, tables) {
4
+ const overlapping = [];
5
+ if (tables.length === 0)
6
+ return overlapping;
7
+ for (const merge of merges) {
8
+ const rect = boundedRect(decodeRange(merge));
9
+ if (rect === undefined)
10
+ continue;
11
+ const table = tables.find((candidate) => rectsOverlap(rect, candidate.region));
12
+ if (table !== undefined)
13
+ overlapping.push({ merge, table });
14
+ }
15
+ return overlapping;
16
+ }
3
17
  export function clearCoveredValues(rows, rect) {
4
18
  for (const [row, cols] of rows) {
5
19
  if (row < rect.top || row > rect.bottom)