@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
@@ -1,9 +1,11 @@
1
1
  import { AuthoringError, quoted } from '../errors.js';
2
2
  import { tokenSet } from '../token-set.js';
3
- import { decodeCellRef, encodeAddress, encodeRect, MAX_COLUMN, MAX_ROW, numberToColumn, } from './address.js';
3
+ import { decodeCellRef, encodeAddress, encodeRect, MAX_COLUMN, MAX_ROW, nameReadsAsReference, numberToColumn, } from './address.js';
4
4
  import { cloneWith } from './clone.js';
5
5
  import { isDeletedSpan, shiftIndex } from './grid-shift.js';
6
+ import { INTERNAL } from './internal.js';
6
7
  import { MAX_TABLE_NAME_LENGTH, TABLE_NAME_PATTERN } from './limits.js';
8
+ import { structuredColumnReference } from './structured-reference.js';
7
9
  const STYLE_INFO_CLONE = {
8
10
  name: (name) => (name === 'None' ? undefined : name),
9
11
  showFirstColumn: 'value',
@@ -11,6 +13,12 @@ const STYLE_INFO_CLONE = {
11
13
  showRowStripes: 'value',
12
14
  showColumnStripes: 'value',
13
15
  };
16
+ export const TABLE_STYLE_FLAGS = [
17
+ 'showFirstColumn',
18
+ 'showLastColumn',
19
+ 'showRowStripes',
20
+ 'showColumnStripes',
21
+ ];
14
22
  function cloneStyleInfo(style) {
15
23
  const clone = cloneWith(style, STYLE_INFO_CLONE);
16
24
  if (clone.name === undefined)
@@ -57,11 +65,13 @@ function validateTableName(name) {
57
65
  throw new SyntaxError(`table name ${quoted(name)} is not a valid Excel identifier: it must start with a letter, ` +
58
66
  'underscore, or backslash and contain only letters, digits, periods, and underscores');
59
67
  }
68
+ if (nameReadsAsReference(name)) {
69
+ throw new SyntaxError(`table name ${quoted(name)} reads as a cell reference, an R1C1 reference or a boolean, ` +
70
+ 'which Excel does not accept as a name');
71
+ }
60
72
  }
61
73
  export class Table {
62
74
  name;
63
- displayName;
64
- columns;
65
75
  headerRow;
66
76
  totalsRow;
67
77
  totalsRowShown;
@@ -70,7 +80,24 @@ export class Table {
70
80
  #anchorCol;
71
81
  #anchorRow;
72
82
  #dataRowCount;
83
+ #columns;
73
84
  #grid;
85
+ [INTERNAL] = {
86
+ rewriteFormulas: (rewrite) => {
87
+ this.#columns = this.#columns.map((column) => {
88
+ const { calculatedColumnFormula: calculated, totalsRowFormula: totals } = column;
89
+ const nextCalculated = calculated === undefined ? undefined : rewrite(calculated);
90
+ const nextTotals = totals === undefined ? undefined : rewrite(totals);
91
+ if (nextCalculated === calculated && nextTotals === totals)
92
+ return column;
93
+ return {
94
+ ...column,
95
+ ...(nextCalculated === undefined ? {} : { calculatedColumnFormula: nextCalculated }),
96
+ ...(nextTotals === undefined ? {} : { totalsRowFormula: nextTotals }),
97
+ };
98
+ });
99
+ },
100
+ };
74
101
  constructor(options, grid) {
75
102
  validateTableName(options.name);
76
103
  if (options.columns.length === 0) {
@@ -84,14 +111,15 @@ export class Table {
84
111
  anchor = decodeCellRef(options.ref);
85
112
  }
86
113
  catch (cause) {
114
+ if (cause instanceof RangeError)
115
+ throw cause;
87
116
  throw new SyntaxError(`table ref ${quoted(options.ref)} must anchor at a single cell (e.g. "A1")`, {
88
117
  cause,
89
118
  });
90
119
  }
91
120
  const { col, row } = anchor;
92
121
  this.name = options.name;
93
- this.displayName = options.displayName ?? options.name;
94
- this.columns = disambiguateColumnNames(options.columns);
122
+ this.#columns = disambiguateColumnNames(options.columns);
95
123
  this.headerRow = options.headerRow ?? true;
96
124
  this.totalsRow = options.totalsRow ?? false;
97
125
  this.totalsRowShown = options.totalsRowShown;
@@ -115,6 +143,9 @@ export class Table {
115
143
  if (grid !== undefined)
116
144
  this.#materializeFrame(grid);
117
145
  }
146
+ get columns() {
147
+ return this.#columns;
148
+ }
118
149
  get columnCount() {
119
150
  return this.columns.length;
120
151
  }
@@ -173,7 +204,7 @@ export class Table {
173
204
  const code = TOTALS_ROW_SUBTOTAL_CODE[column.totalsRowFunction];
174
205
  if (code !== undefined) {
175
206
  grid.writeCell(bottom, col, {
176
- formula: `SUBTOTAL(${code},${this.name}[${column.name}])`,
207
+ formula: `SUBTOTAL(${code},${structuredColumnReference(this.name, column.name)})`,
177
208
  });
178
209
  }
179
210
  else if (column.totalsRowFunction === 'custom' && column.totalsRowFormula !== undefined) {
@@ -208,7 +239,6 @@ export class Table {
208
239
  get options() {
209
240
  const options = {
210
241
  name: this.name,
211
- displayName: this.displayName,
212
242
  ref: encodeAddress(this.#anchorCol, this.#anchorRow),
213
243
  columns: this.columns.map((column) => ({ ...column })),
214
244
  rowCount: this.#dataRowCount,
@@ -48,6 +48,8 @@ export interface ThemeOverrides {
48
48
  readonly colors?: Readonly<Partial<Record<ThemeColorSlot, string>>> | undefined;
49
49
  readonly fonts?: ThemeFontScheme | undefined;
50
50
  }
51
+ /** Whether a value is the bare 6-hex RGB a theme colour slot holds, in either casing. */
52
+ export declare function isThemeRgb(value: string): boolean;
51
53
  /** Reduce an authored theme colour to the bare 6-hex RGB DrawingML wants.
52
54
  *
53
55
  * A theme colour is a bare 6-hex RGB: DrawingML has no alpha channel on `<a:srgbClr val>`. The two
@@ -37,10 +37,13 @@ export const DEFAULT_THEME_FONTS = {
37
37
  major: 'Calibri Light',
38
38
  minor: OFFICE_BODY_FACE,
39
39
  };
40
+ export function isThemeRgb(value) {
41
+ return /^[0-9A-Fa-f]{6}$/.test(value);
42
+ }
40
43
  export function normalizeThemeColor(value) {
41
44
  const argb = parseArgb(value);
42
45
  if (argb === undefined) {
43
- throw new SyntaxError(`invalid theme colour ${quoted(value)}: expected 6 hexadecimal digits (RRGGBB)`);
46
+ throw new SyntaxError(`invalid theme colour ${quoted(value)}: expected RRGGBB or AARRGGBB hexadecimal digits, optionally after '#'`);
44
47
  }
45
48
  return argb.slice(2).toUpperCase();
46
49
  }
@@ -1,5 +1,5 @@
1
1
  import type { Font } from './style.ts';
2
- /** The observable kind of a cell's value. Both formula shapes report as `Formula`. */
2
+ /** The observable kind of a cell's value. Every formula kind reports as `Formula`. */
3
3
  export declare const ValueType: {
4
4
  readonly Null: 'null';
5
5
  readonly Number: 'number';
@@ -9,11 +9,19 @@ export declare const ValueType: {
9
9
  readonly Error: 'error';
10
10
  readonly Formula: 'formula';
11
11
  readonly RichText: 'richText';
12
- readonly Hyperlink: 'hyperlink';
13
12
  };
14
13
  export type ValueType = (typeof ValueType)[keyof typeof ValueType];
15
- /** The canonical Excel error literals a cell (or formula result) can carry. */
16
- export declare const ERROR_CODES: readonly ['#N/A', '#REF!', '#NAME?', '#DIV/0!', '#NULL!', '#VALUE!', '#NUM!', '#SPILL!', '#CALC!', '#GETTING_DATA'];
14
+ /**
15
+ * The errors a cell (or formula result) can hold.
16
+ *
17
+ * They are stored two ways. The classic seven, `#GETTING_DATA` and `#BUSY!` are literals: a typed cell
18
+ * holds the spelling and Excel reads it back as that error. Excel has no literal for `#SPILL!`,
19
+ * `#CONNECT!`, `#BLOCKED!`, `#UNKNOWN!`, `#FIELD!` and `#CALC!`, and stores each as `#VALUE!` beside a
20
+ * rich value naming the real one, which is how they are read and written here. `#PYTHON!`, `#EXTERNAL!`
21
+ * and `#TIMEOUT!` are not here: Excel reads no literal spelling of them, and no rich value was seen to
22
+ * read back as one of them unambiguously (Excel 16.0 build 20326).
23
+ */
24
+ export declare const ERROR_CODES: readonly ['#N/A', '#REF!', '#NAME?', '#DIV/0!', '#NULL!', '#VALUE!', '#NUM!', '#GETTING_DATA', '#BUSY!', '#SPILL!', '#CONNECT!', '#BLOCKED!', '#UNKNOWN!', '#FIELD!', '#CALC!'];
17
25
  export type ErrorCode = (typeof ERROR_CODES)[number];
18
26
  /**
19
27
  * The error a spreadsheet puts in place of a reference that has nowhere left to point.
@@ -37,16 +45,6 @@ export interface RichTextRun {
37
45
  export interface RichTextValue {
38
46
  readonly richText: readonly RichTextRun[];
39
47
  }
40
- /** A hyperlink cell: a URL plus the text (plain or rich) shown in the cell. */
41
- export interface HyperlinkValue {
42
- readonly hyperlink: string;
43
- readonly text: string | RichTextValue;
44
- readonly tooltip?: string;
45
- /** The clickable extent (`'D1:H1'`) when the link spans a range whose top-left corner is this
46
- * cell. Absent for an ordinary single-cell link. The destination and label live on the top-left
47
- * cell; `range` records how far Excel highlights the clickable area so it survives a round-trip. */
48
- readonly range?: string;
49
- }
50
48
  /** The cached result a formula carries: any scalar, a date, or an error. */
51
49
  export type FormulaResult = number | string | boolean | Date | ErrorValue;
52
50
  /** A cell whose value is computed by its own formula. */
@@ -68,6 +66,26 @@ export interface SharedFormulaValue {
68
66
  readonly formula?: string;
69
67
  readonly result?: FormulaResult;
70
68
  }
69
+ /**
70
+ * A cell holding an array formula (`<f t="array">`): one formula whose result fills {@link ref}, a
71
+ * range starting at this cell. The other cells of the range hold only the values the formula
72
+ * produced, which is how Excel stores them, so they are plain values here too.
73
+ *
74
+ * Excel stores two kinds this way. A legacy array formula, entered with Ctrl+Shift+Enter, shows in
75
+ * braces and fills the range it was entered over. A dynamic-array formula is {@link dynamic}: it
76
+ * spills, `ref` is the range its last calculation filled, and no braces are shown. Excel keeps that
77
+ * mark in the workbook's cell metadata rather than on the formula, and a formula that loses it opens
78
+ * as a legacy array formula that no longer spills.
79
+ */
80
+ export interface ArrayFormulaValue {
81
+ readonly shareType: 'array';
82
+ readonly formula: string;
83
+ /** The range the result fills, starting at this cell: `'B1:B3'`, or `'B1'` for one cell. */
84
+ readonly ref: string;
85
+ /** Whether Excel evaluates the formula as a dynamic array rather than a Ctrl+Shift+Enter one. */
86
+ readonly dynamic?: boolean;
87
+ readonly result?: FormulaResult;
88
+ }
71
89
  /**
72
90
  * A cell computed by a What-If-Analysis data table (`<f t="dataTable">`), the OOXML formula kind that
73
91
  * fills a range by re-evaluating a model against a grid of substituted input cells. The library does
@@ -86,25 +104,36 @@ export interface DataTableFormulaValue {
86
104
  readonly r1?: string;
87
105
  /** The second (column) input-cell reference, present for a 2-D table. */
88
106
  readonly r2?: string;
107
+ /**
108
+ * Whether the cell {@link r1} named has been deleted. Excel keeps the reference as it was written and
109
+ * sets this flag, and the table then shows `#REF!`; a row or column delete that takes the input cell
110
+ * does the same here.
111
+ */
112
+ readonly r1Deleted?: boolean;
113
+ /** Whether the cell {@link r2} named has been deleted, as {@link r1Deleted} is for {@link r1}. */
114
+ readonly r2Deleted?: boolean;
89
115
  readonly result?: FormulaResult;
90
116
  }
91
117
  /** Everything a cell's value can be. `null` is the empty cell. */
92
- export type CellValue = null | number | string | boolean | Date | ErrorValue | FormulaValue | SharedFormulaValue | DataTableFormulaValue | RichTextValue | HyperlinkValue;
118
+ export type CellValue = null | number | string | boolean | Date | ErrorValue | FormulaValue | SharedFormulaValue | ArrayFormulaValue | DataTableFormulaValue | RichTextValue;
93
119
  /**
94
120
  * Whether a value is an in-cell error ({@link ErrorValue}). The narrowing counterpart of
95
121
  * `detectValueType(value) === ValueType.Error`: use this one when the branch goes on to read
96
- * `.error`, and {@link detectValueType} when it dispatches over all nine kinds at once.
122
+ * `.error`, and {@link detectValueType} when it dispatches over all eight kinds at once.
97
123
  */
98
124
  export declare function isErrorValue(value: CellValue): value is ErrorValue;
99
125
  /**
100
- * Whether a value is a cell's own formula ({@link FormulaValue}): a master, or a formula
101
- * belonging to no shared group. A shared-formula clone is **not** one of these; see
102
- * {@link isSharedFormulaValue}. Both report as `ValueType.Formula`, so a caller that means "any
103
- * formula-shaped cell" wants {@link detectValueType}, not this.
126
+ * Whether a value is a cell's own plain formula ({@link FormulaValue}): a shared-formula master, or a
127
+ * formula belonging to no group. A shared-formula clone is **not** one of these, and nor is an array
128
+ * formula; see {@link isSharedFormulaValue} and {@link isArrayFormulaValue}. Every formula kind reports
129
+ * as `ValueType.Formula`, so a caller that means "any formula-shaped cell" wants
130
+ * {@link detectValueType}, not this.
104
131
  */
105
132
  export declare function isFormulaValue(value: CellValue): value is FormulaValue;
106
133
  /** Whether a value is a clone participating in a shared formula ({@link SharedFormulaValue}). */
107
134
  export declare function isSharedFormulaValue(value: CellValue): value is SharedFormulaValue;
135
+ /** Whether a value is an array formula, legacy or dynamic ({@link ArrayFormulaValue}). */
136
+ export declare function isArrayFormulaValue(value: CellValue): value is ArrayFormulaValue;
108
137
  /** Whether a value is a What-If-Analysis data-table formula ({@link DataTableFormulaValue}). */
109
138
  export declare function isDataTableFormulaValue(value: CellValue): value is DataTableFormulaValue;
110
139
  /**
@@ -112,11 +141,6 @@ export declare function isDataTableFormulaValue(value: CellValue): value is Data
112
141
  * make before {@link richTextToPlain}, which accepts nothing else.
113
142
  */
114
143
  export declare function isRichTextValue(value: CellValue): value is RichTextValue;
115
- /**
116
- * Whether a value is a hyperlink ({@link HyperlinkValue}). Note that its `text` is itself either
117
- * a string or a {@link RichTextValue}, so reading the label out means one more narrowing.
118
- */
119
- export declare function isHyperlinkValue(value: CellValue): value is HyperlinkValue;
120
144
  /**
121
145
  * Flatten a rich-text value to its plain text by concatenating every run's text in order. This is the
122
146
  * text a consumer that cannot render per-run formatting (a CSV field, a pivot cache entry) sees, and
@@ -137,9 +161,8 @@ export declare function richTextToPlain(value: RichTextValue): string;
137
161
  * - a `Date` → a full ISO-8601 timestamp
138
162
  * - an error → its literal, e.g. `"#REF!"`, the same string the grid shows
139
163
  * - rich text → every run concatenated ({@link richTextToPlain})
140
- * - a hyperlinkits label, never its destination
141
- * - any of the three formula kinds the text of the *cached result*, and `""` when the cell
142
- * carries no cached result: the formula source is not text the sheet ever displayed
164
+ * - any formula kind the text of the *cached result*, and `""` when the cell carries no cached
165
+ * result: the formula source is not text the sheet ever displayed
143
166
  */
144
167
  export declare function cellValueToText(value: CellValue): string;
145
168
  /**
@@ -7,7 +7,6 @@ export const ValueType = {
7
7
  Error: 'error',
8
8
  Formula: 'formula',
9
9
  RichText: 'richText',
10
- Hyperlink: 'hyperlink',
11
10
  };
12
11
  export const ERROR_CODES = [
13
12
  '#N/A',
@@ -17,9 +16,14 @@ export const ERROR_CODES = [
17
16
  '#NULL!',
18
17
  '#VALUE!',
19
18
  '#NUM!',
19
+ '#GETTING_DATA',
20
+ '#BUSY!',
20
21
  '#SPILL!',
22
+ '#CONNECT!',
23
+ '#BLOCKED!',
24
+ '#UNKNOWN!',
25
+ '#FIELD!',
21
26
  '#CALC!',
22
- '#GETTING_DATA',
23
27
  ];
24
28
  export const REF_ERROR = '#REF!';
25
29
  const ERROR_SET = new Set(ERROR_CODES);
@@ -30,20 +34,20 @@ export function isErrorValue(value) {
30
34
  return hasKey(value, 'error');
31
35
  }
32
36
  export function isFormulaValue(value) {
33
- return hasKey(value, 'formula') && !('sharedFormula' in value);
37
+ return hasKey(value, 'formula') && !('sharedFormula' in value) && !('shareType' in value);
34
38
  }
35
39
  export function isSharedFormulaValue(value) {
36
40
  return hasKey(value, 'sharedFormula');
37
41
  }
42
+ export function isArrayFormulaValue(value) {
43
+ return hasKey(value, 'shareType') && value.shareType === 'array';
44
+ }
38
45
  export function isDataTableFormulaValue(value) {
39
46
  return hasKey(value, 'shareType') && value.shareType === 'dataTable';
40
47
  }
41
48
  export function isRichTextValue(value) {
42
49
  return hasKey(value, 'richText');
43
50
  }
44
- export function isHyperlinkValue(value) {
45
- return hasKey(value, 'hyperlink');
46
- }
47
51
  export function richTextToPlain(value) {
48
52
  return value.richText.map((run) => run.text).join('');
49
53
  }
@@ -68,9 +72,10 @@ function classify(value, visit) {
68
72
  }
69
73
  if (value instanceof Date)
70
74
  return visit.date(value);
71
- if (isHyperlinkValue(value))
72
- return visit.hyperlink(value);
73
- if (isFormulaValue(value) || isSharedFormulaValue(value) || isDataTableFormulaValue(value)) {
75
+ if (isFormulaValue(value) ||
76
+ isSharedFormulaValue(value) ||
77
+ isArrayFormulaValue(value) ||
78
+ isDataTableFormulaValue(value)) {
74
79
  return visit.formula(value);
75
80
  }
76
81
  if (isRichTextValue(value))
@@ -88,7 +93,6 @@ const TO_TYPE = {
88
93
  error: () => ValueType.Error,
89
94
  formula: () => ValueType.Formula,
90
95
  richText: () => ValueType.RichText,
91
- hyperlink: () => ValueType.Hyperlink,
92
96
  };
93
97
  const TO_TEXT = {
94
98
  null: () => '',
@@ -99,7 +103,6 @@ const TO_TEXT = {
99
103
  error: (value) => value.error,
100
104
  formula: (value) => (value.result === undefined ? '' : cellValueToText(value.result)),
101
105
  richText: richTextToPlain,
102
- hyperlink: (value) => (typeof value.text === 'string' ? value.text : richTextToPlain(value.text)),
103
106
  };
104
107
  function unsupportedValue(value) {
105
108
  throw new TypeError(`unsupported cell value: ${describe(value)}`);
@@ -117,7 +120,7 @@ export function coerceCellValue(value) {
117
120
  if (value === undefined)
118
121
  return null;
119
122
  detectValueType(value);
120
- if (isFormulaValue(value)) {
123
+ if (isFormulaValue(value) || isArrayFormulaValue(value)) {
121
124
  const formula = stripLeadingEquals(value.formula);
122
125
  return formula === value.formula ? value : { ...value, formula };
123
126
  }
@@ -43,7 +43,7 @@ export type DifferentialStyle = Pick<CellStyle, 'font' | 'numFmt' | 'fill' | 'bo
43
43
  * are held decoded.
44
44
  */
45
45
  export interface TableStyleTable {
46
- readonly styles: readonly string[];
46
+ readonly styles: readonly TableStyleDefinition[];
47
47
  readonly defaultTableStyle?: string | undefined;
48
48
  readonly defaultPivotStyle?: string | undefined;
49
49
  /**
@@ -59,6 +59,21 @@ export interface TableStyleTable {
59
59
  */
60
60
  readonly namespaces?: readonly TableStyleNamespace[];
61
61
  }
62
+ /**
63
+ * One preserved `<tableStyle>` definition: its bytes, and the `name` a table's style info matches it by.
64
+ *
65
+ * The name travels beside the fragment rather than being read back out of it. An authored style
66
+ * replaces the preserved definition it shares a name with, and a pattern pulling `name="…"` out of
67
+ * the bytes did not see `name='…'`, which is the same attribute in single quotes: the writer emitted
68
+ * both definitions under one name, the duplicate {@link Workbook.addTableStyle} promises cannot happen.
69
+ * The reader takes the name from the scanner that read the element, decoded, whatever its quoting.
70
+ */
71
+ export interface TableStyleDefinition {
72
+ /** The style's `name`, decoded; empty for a fragment that declared none. */
73
+ readonly name: string;
74
+ /** The `<tableStyle>…</tableStyle>` element exactly as the file spelled it. */
75
+ readonly xml: string;
76
+ }
62
77
  /** One namespace declaration a preserved `<tableStyle>` fragment depends on. */
63
78
  export interface TableStyleNamespace {
64
79
  readonly prefix: string;
@@ -85,6 +100,7 @@ export declare class WorkbookStyleTables {
85
100
  addCustomTableStyle(style: TableStyle): void;
86
101
  indexedPalette(): readonly string[];
87
102
  restoreDifferentialStyles(fragments: readonly string[]): void;
103
+ adoptDifferentialStyle(fragment: string): number;
88
104
  restoreNamedStyles(styles: readonly NamedCellStyle[]): void;
89
105
  restoreIndexedColors(fragments: readonly string[]): void;
90
106
  restoreMruColors(fragments: readonly string[]): void;
@@ -33,6 +33,10 @@ export class WorkbookStyleTables {
33
33
  restoreDifferentialStyles(fragments) {
34
34
  replaceContents(this.#differentialStyles, fragments);
35
35
  }
36
+ adoptDifferentialStyle(fragment) {
37
+ const existing = this.#differentialStyles.indexOf(fragment);
38
+ return existing === -1 ? this.#differentialStyles.push(fragment) - 1 : existing;
39
+ }
36
40
  restoreNamedStyles(styles) {
37
41
  replaceContents(this.#namedStyles, styles);
38
42
  }
@@ -19,9 +19,11 @@ export class WorkbookTheme {
19
19
  this.#declared = declared;
20
20
  }
21
21
  author(overrides) {
22
- for (const value of Object.values(overrides.colors ?? {}))
23
- normalizeThemeColor(value);
24
- Object.assign(this.#authored.colors, overrides.colors ?? {});
22
+ const colors = Object.fromEntries(Object.entries(overrides.colors ?? {}).map(([slot, value]) => [
23
+ slot,
24
+ normalizeThemeColor(value),
25
+ ]));
26
+ Object.assign(this.#authored.colors, colors);
25
27
  this.#authored.fonts = { ...this.#authored.fonts, ...overrides.fonts };
26
28
  }
27
29
  get colors() {
@@ -2,7 +2,8 @@ import { type CustomUiDocument } from '../customui/index.ts';
2
2
  import { type VbaLibraryReference, type VbaProject, type VbaProjectSignature } from '../vba/index.ts';
3
3
  import { type Person } from './comment-thread.ts';
4
4
  import type { DateEpoch } from './date.ts';
5
- import type { WorkbookImage, WorksheetImages } from './image.ts';
5
+ import { type SheetSplice } from './formula-references.ts';
6
+ import { type WorkbookImage, type WorksheetImages } from './image.ts';
6
7
  import { INTERNAL } from './internal.ts';
7
8
  import type { PreservedPart, PreservedRootReference } from './preserved.ts';
8
9
  import type { Color, Font } from './style.ts';
@@ -345,7 +346,8 @@ export declare class Workbook {
345
346
  * slot left unauthored keeps the source's own encoding, including the `<a:sysClr>` form Excel uses
346
347
  * for `dk1`/`lt1` so they follow the viewer's window colours.
347
348
  *
348
- * @throws {AuthoringError} if a colour is not 6 or 8 hexadecimal digits.
349
+ * @throws {SyntaxError} if a colour is not 6 or 8 hexadecimal digits, with or without a leading `#`.
350
+ * Every colour is read before any is stored, so a refused call changes nothing.
349
351
  */
350
352
  setTheme(overrides: ThemeOverrides): void;
351
353
  /**
@@ -354,6 +356,9 @@ export declare class Workbook {
354
356
  *
355
357
  * Note the slot *order*: `theme="0"` is `lt1` and `theme="1"` is `dk1`, which is not the order the
356
358
  * slots appear in the theme part. See {@link THEME_COLOR_SLOTS}.
359
+ *
360
+ * Every slot reads back as six uppercase hexadecimal digits, whichever spelling {@link setTheme} was
361
+ * given or the theme part used.
357
362
  */
358
363
  get themeColors(): ThemeColorScheme;
359
364
  /** The theme's major (heading) and minor (body) typefaces, authored values over the source's. */
@@ -413,7 +418,8 @@ export declare class Workbook {
413
418
  *
414
419
  * A serializer composes these *over* the existing part rather than generating one from scratch, so
415
420
  * a preserved theme keeps its format scheme, its unauthored slots' exact encoding, and the
416
- * relationships it carries.
421
+ * relationships it carries. Its colours are already reduced to the six uppercase hexadecimal digits
422
+ * a slot holds.
417
423
  */
418
424
  get themeOverrides(): ThemeOverrides | undefined;
419
425
  /**
@@ -561,6 +567,12 @@ export interface WorkbookInternals {
561
567
  * re-write.
562
568
  */
563
569
  restoreDifferentialStyles(fragments: readonly string[]): void;
570
+ /**
571
+ * The index of a `<dxf>` fragment a sheet carried inline, found among the differential styles or
572
+ * appended after them, so a conditional format read from the extension form holds a `dxfId` like any
573
+ * other rule.
574
+ */
575
+ adoptDifferentialStyle(fragment: string): number;
564
576
  /**
565
577
  * Reinstate the custom indexed-color palette (`<colors><indexedColors>`) read from a file, each
566
578
  * entry a verbatim `<rgbColor rgb="…"/>` fragment, so a colour referenced by `indexed="…"` keeps
@@ -617,4 +629,9 @@ export interface WorkbookInternals {
617
629
  * {@link Workbook.addPerson} is the authoring verb.
618
630
  */
619
631
  restorePersons(persons: readonly Person[]): void;
632
+ /**
633
+ * Every row and column splice made to the workbook's sheets, in the order they were made, for the
634
+ * writer to replay over the references a preserved part spells.
635
+ */
636
+ splices(): readonly SheetSplice[];
620
637
  }
@@ -2,6 +2,8 @@ import { isCustomUiRelType, parseCustomUi } from '../customui/index.js';
2
2
  import { AuthoringError, quoted } from '../errors.js';
3
3
  import { addVbaReference, removeVbaModule, } from '../vba/index.js';
4
4
  import { commentThreadGuid } from './comment-thread.js';
5
+ import { spliceFormula } from './formula-references.js';
6
+ import { pictureProperties } from './image.js';
5
7
  import { INTERNAL } from './internal.js';
6
8
  import { INVALID_SHEET_NAME_CHARS, MAX_SHEET_NAME_LENGTH } from './limits.js';
7
9
  import { checkTableStyle } from './table-style.js';
@@ -32,6 +34,7 @@ export class Workbook {
32
34
  #styles = new WorkbookStyleTables();
33
35
  #theme = new WorkbookTheme(() => this.#styles.indexedPalette());
34
36
  #preservedReferences = [];
37
+ #splices = [];
35
38
  #persons = new Map();
36
39
  get worksheets() {
37
40
  return this.#worksheets;
@@ -192,9 +195,10 @@ export class Workbook {
192
195
  exportImages(sheet) {
193
196
  const background = sheet.backgroundImageId;
194
197
  return {
195
- anchored: sheet.images.map(({ imageId, anchor }) => ({
196
- image: this.#media.require(imageId, sheet.name),
197
- anchor,
198
+ anchored: sheet.images.map((picture) => ({
199
+ image: this.#media.require(picture.imageId, sheet.name),
200
+ anchor: picture.anchor,
201
+ ...pictureProperties(picture),
198
202
  })),
199
203
  background: background === undefined ? undefined : this.#media.require(background, sheet.name),
200
204
  };
@@ -203,8 +207,8 @@ export class Workbook {
203
207
  for (const id of new Set(sheet.images.map((image) => image.imageId)))
204
208
  sheet.removeImage(id);
205
209
  sheet.removeBackgroundImage();
206
- for (const { image, anchor } of images.anchored) {
207
- sheet.addImageAnchor(this.#media.registerExisting(image), anchor);
210
+ for (const picture of images.anchored) {
211
+ sheet.addImageAnchor(this.#media.registerExisting(picture.image), picture.anchor, pictureProperties(picture));
208
212
  }
209
213
  if (images.background !== undefined) {
210
214
  sheet.addBackgroundImage(this.#media.registerExisting(images.background));
@@ -225,9 +229,24 @@ export class Workbook {
225
229
  addWorksheet(name, options = {}) {
226
230
  this.#assertValidSheetName(name);
227
231
  const sheet = new Worksheet(name, this.#nextSheetId++, options.state ?? 'visible');
232
+ sheet[INTERNAL].setFormulaHost((edit) => {
233
+ this.#spliceFormulasBeyond(sheet, edit);
234
+ });
228
235
  this.#worksheets.push(sheet);
229
236
  return sheet;
230
237
  }
238
+ #spliceFormulasBeyond(spliced, edit) {
239
+ this.#splices.push(edit);
240
+ for (const sheet of this.#worksheets) {
241
+ if (sheet !== spliced)
242
+ sheet[INTERNAL].spliceFormulas(edit);
243
+ }
244
+ for (const [index, definedName] of this.#definedNames.entries()) {
245
+ const refersTo = spliceFormula(definedName.refersTo, definedName.scope, edit);
246
+ if (refersTo !== definedName.refersTo)
247
+ this.#definedNames[index] = { ...definedName, refersTo };
248
+ }
249
+ }
231
250
  getWorksheet(nameOrId) {
232
251
  if (typeof nameOrId === 'number') {
233
252
  return this.#worksheets.find((sheet) => sheet.id === nameOrId);
@@ -273,6 +292,7 @@ export class Workbook {
273
292
  restoreDifferentialStyles: (fragments) => {
274
293
  this.#styles.restoreDifferentialStyles(fragments);
275
294
  },
295
+ adoptDifferentialStyle: (fragment) => this.#styles.adoptDifferentialStyle(fragment),
276
296
  restoreIndexedColors: (fragments) => {
277
297
  this.#styles.restoreIndexedColors(fragments);
278
298
  },
@@ -296,5 +316,6 @@ export class Workbook {
296
316
  for (const person of persons)
297
317
  this.#persons.set(person.id, person);
298
318
  },
319
+ splices: () => this.#splices,
299
320
  };
300
321
  }
@@ -20,13 +20,20 @@ export declare class WorksheetMerges {
20
20
  get rects(): readonly MergeRect[];
21
21
  /**
22
22
  * Declare a merged range, returning the rectangle it covers, or `undefined` for an unbounded
23
- * whole-row/column range, which is declared and overlap-checks against nothing.
23
+ * whole-row/column range, which is declared and overlap-checks against nothing. The range is stored
24
+ * in canonical form (see {@link canonicalMerge}).
24
25
  *
26
+ * @throws {SyntaxError} if the range is unparseable or names a worksheet.
25
27
  * @throws {AuthoringError} if the range overlaps an already-merged region. Excel forbids
26
28
  * overlapping merges and writes such geometry as a file it then offers to repair.
27
29
  */
28
30
  add(range: string): MergeRect | undefined;
29
- /** Drop a declared range and, with it, the rectangle it covers. The inverse of {@link add}. */
31
+ /**
32
+ * Drop a declared range and, with it, the rectangle it covers. The inverse of {@link add}, and it
33
+ * matches however the range is spelled, since both sides are compared in canonical form.
34
+ *
35
+ * @throws {SyntaxError} if the range is unparseable or names a worksheet.
36
+ */
30
37
  remove(range: string): MergeRemoval;
31
38
  /**
32
39
  * Resolve a position to the top-left of the region covering it, or to itself when none does. This
@@ -38,7 +45,5 @@ export declare class WorksheetMerges {
38
45
  };
39
46
  /** Replace every declared range and rectangle at once: what a structural splice re-anchors to. */
40
47
  replaceAll(ranges: readonly string[], rects: readonly MergeRect[]): void;
41
- /** Report that something outside has rewritten the grid under these regions. */
42
- invalidate(): void;
43
48
  clear(): void;
44
49
  }