@shbernal/ts-xlsx 1.3.1 → 2.1.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 (186) hide show
  1. package/README.md +20 -10
  2. package/dist/bytes.d.ts +24 -0
  3. package/dist/bytes.js +34 -0
  4. package/dist/core/address.d.ts +72 -3
  5. package/dist/core/address.js +50 -10
  6. package/dist/core/autofilter.d.ts +13 -2
  7. package/dist/core/autofilter.js +35 -10
  8. package/dist/core/cell.d.ts +17 -16
  9. package/dist/core/cell.js +3 -7
  10. package/dist/core/color-resolution.d.ts +8 -8
  11. package/dist/core/color-resolution.js +7 -8
  12. package/dist/core/column.d.ts +8 -9
  13. package/dist/core/column.js +2 -4
  14. package/dist/core/comment-thread.d.ts +32 -23
  15. package/dist/core/comment-thread.js +4 -4
  16. package/dist/core/conditional-formatting-overlay.d.ts +6 -1
  17. package/dist/core/conditional-formatting-overlay.js +11 -0
  18. package/dist/core/conditional-formatting.d.ts +48 -8
  19. package/dist/core/conditional-formatting.js +88 -0
  20. package/dist/core/data-validation-overlay.d.ts +12 -1
  21. package/dist/core/data-validation-overlay.js +21 -2
  22. package/dist/core/data-validation.d.ts +12 -5
  23. package/dist/core/data-validation.js +34 -0
  24. package/dist/core/date.d.ts +3 -3
  25. package/dist/core/formula.d.ts +6 -6
  26. package/dist/core/grid-edits.d.ts +16 -0
  27. package/dist/core/grid-edits.js +29 -11
  28. package/dist/core/grid-shift.d.ts +19 -0
  29. package/dist/core/grid-shift.js +8 -0
  30. package/dist/core/image.d.ts +39 -2
  31. package/dist/core/image.js +10 -0
  32. package/dist/core/internal.d.ts +9 -1
  33. package/dist/core/limits.d.ts +4 -4
  34. package/dist/core/merge.d.ts +17 -12
  35. package/dist/core/merge.js +36 -5
  36. package/dist/core/page-setup.d.ts +20 -6
  37. package/dist/core/page-setup.js +8 -1
  38. package/dist/core/pivot-table.d.ts +4 -4
  39. package/dist/core/pivot-table.js +29 -20
  40. package/dist/core/preserved.d.ts +3 -3
  41. package/dist/core/protection.d.ts +7 -4
  42. package/dist/core/protection.js +20 -14
  43. package/dist/core/range.d.ts +16 -14
  44. package/dist/core/range.js +5 -5
  45. package/dist/core/row-input.d.ts +9 -8
  46. package/dist/core/row-input.js +12 -10
  47. package/dist/core/row.d.ts +7 -8
  48. package/dist/core/row.js +2 -4
  49. package/dist/core/style.d.ts +41 -14
  50. package/dist/core/style.js +82 -67
  51. package/dist/core/table-style.d.ts +9 -9
  52. package/dist/core/table-style.js +1 -1
  53. package/dist/core/table.d.ts +53 -51
  54. package/dist/core/table.js +83 -41
  55. package/dist/core/theme.d.ts +16 -44
  56. package/dist/core/theme.js +6 -148
  57. package/dist/core/value.d.ts +7 -7
  58. package/dist/core/workbook-protection.d.ts +2 -2
  59. package/dist/core/workbook-styles.d.ts +26 -0
  60. package/dist/core/workbook-styles.js +48 -0
  61. package/dist/core/workbook-theme.d.ts +25 -0
  62. package/dist/core/workbook-theme.js +49 -0
  63. package/dist/core/workbook-vba.d.ts +17 -0
  64. package/dist/core/workbook-vba.js +79 -0
  65. package/dist/core/workbook.d.ts +133 -87
  66. package/dist/core/workbook.js +74 -133
  67. package/dist/core/worksheet-comments.d.ts +17 -0
  68. package/dist/core/worksheet-comments.js +70 -0
  69. package/dist/core/worksheet-model.d.ts +4 -4
  70. package/dist/core/worksheet-model.js +1 -0
  71. package/dist/core/worksheet-pictures.d.ts +29 -0
  72. package/dist/core/worksheet-pictures.js +42 -0
  73. package/dist/core/worksheet.d.ts +100 -79
  74. package/dist/core/worksheet.js +63 -142
  75. package/dist/customui/errors.d.ts +1 -1
  76. package/dist/customui/ribbon.d.ts +3 -3
  77. package/dist/customui/ribbon.js +24 -23
  78. package/dist/entries/core.d.ts +7 -7
  79. package/dist/entries/core.js +1 -1
  80. package/dist/entries/csv.d.ts +1 -1
  81. package/dist/entries/node-unavailable.d.ts +15 -0
  82. package/dist/entries/node-unavailable.js +20 -0
  83. package/dist/entries/node.d.ts +1 -0
  84. package/dist/entries/node.js +1 -0
  85. package/dist/entries/vba.d.ts +1 -1
  86. package/dist/entries/vba.js +1 -1
  87. package/dist/entries/xlsx.d.ts +1 -1
  88. package/dist/entries/xlsx.js +1 -1
  89. package/dist/errors.d.ts +12 -12
  90. package/dist/io/csv/read.js +2 -3
  91. package/dist/io/csv/write.d.ts +19 -4
  92. package/dist/io/csv/write.js +34 -4
  93. package/dist/io/opc/errors.d.ts +6 -6
  94. package/dist/io/opc/inflate.d.ts +1 -1
  95. package/dist/io/opc/inflate.js +2 -12
  96. package/dist/io/opc/namespaces.d.ts +1 -1
  97. package/dist/io/opc/read-opc.d.ts +19 -3
  98. package/dist/io/opc/read-opc.js +14 -8
  99. package/dist/io/opc/read-options.d.ts +2 -2
  100. package/dist/io/opc/rels.d.ts +11 -0
  101. package/dist/io/opc/rels.js +3 -2
  102. package/dist/io/opc/sniff-format.d.ts +2 -2
  103. package/dist/io/style/xf-style.d.ts +44 -7
  104. package/dist/io/style/xf-style.js +27 -0
  105. package/dist/io/xlsb/errors.d.ts +1 -1
  106. package/dist/io/xlsb/formula.d.ts +4 -4
  107. package/dist/io/xlsb/primitives.d.ts +8 -8
  108. package/dist/io/xlsb/ptg-functions.d.ts +1 -1
  109. package/dist/io/xlsb/read-styles.js +2 -20
  110. package/dist/io/xlsb/read.d.ts +2 -2
  111. package/dist/io/xlsx/cell-accumulator.d.ts +26 -9
  112. package/dist/io/xlsx/cell-accumulator.js +89 -46
  113. package/dist/io/xlsx/cell-value.d.ts +3 -3
  114. package/dist/io/xlsx/cell-value.js +9 -6
  115. package/dist/io/xlsx/color-xml.d.ts +1 -1
  116. package/dist/io/xlsx/color-xml.js +18 -23
  117. package/dist/io/xlsx/comments.d.ts +5 -5
  118. package/dist/io/xlsx/comments.js +17 -8
  119. package/dist/io/xlsx/conditional-formatting.d.ts +4 -4
  120. package/dist/io/xlsx/conditional-formatting.js +64 -58
  121. package/dist/io/xlsx/data-validation.d.ts +3 -3
  122. package/dist/io/xlsx/data-validation.js +35 -33
  123. package/dist/io/xlsx/errors.d.ts +3 -3
  124. package/dist/io/xlsx/hyperlinks.d.ts +4 -2
  125. package/dist/io/xlsx/hyperlinks.js +10 -18
  126. package/dist/io/xlsx/images.d.ts +1 -1
  127. package/dist/io/xlsx/images.js +25 -28
  128. package/dist/io/xlsx/namespaces.d.ts +2 -2
  129. package/dist/io/xlsx/package-plan.js +5 -2
  130. package/dist/io/xlsx/read-pivot.js +4 -13
  131. package/dist/io/xlsx/read-rows.d.ts +13 -13
  132. package/dist/io/xlsx/read-rows.js +19 -55
  133. package/dist/io/xlsx/read-shared-strings.js +7 -44
  134. package/dist/io/xlsx/read-styles.d.ts +3 -3
  135. package/dist/io/xlsx/read-styles.js +42 -66
  136. package/dist/io/xlsx/read-worksheet.js +73 -129
  137. package/dist/io/xlsx/read.d.ts +4 -6
  138. package/dist/io/xlsx/read.js +104 -142
  139. package/dist/io/xlsx/rich-runs.d.ts +41 -7
  140. package/dist/io/xlsx/rich-runs.js +94 -30
  141. package/dist/io/xlsx/rich-text.d.ts +1 -1
  142. package/dist/io/xlsx/shared-strings.d.ts +2 -2
  143. package/dist/io/xlsx/sheet-properties.d.ts +1 -1
  144. package/dist/io/xlsx/sheet-properties.js +14 -10
  145. package/dist/io/xlsx/styles.d.ts +9 -22
  146. package/dist/io/xlsx/styles.js +65 -89
  147. package/dist/io/xlsx/tables.d.ts +2 -2
  148. package/dist/io/xlsx/tables.js +20 -25
  149. package/dist/io/xlsx/theme-xml.d.ts +37 -0
  150. package/dist/io/xlsx/theme-xml.js +145 -0
  151. package/dist/io/xlsx/threaded-comments.d.ts +18 -27
  152. package/dist/io/xlsx/threaded-comments.js +26 -38
  153. package/dist/io/xlsx/workbook-xml.js +14 -8
  154. package/dist/io/xlsx/worksheet-xml.d.ts +2 -2
  155. package/dist/io/xlsx/worksheet-xml.js +40 -48
  156. package/dist/io/xlsx/write-stream.d.ts +23 -16
  157. package/dist/io/xlsx/write-stream.js +13 -14
  158. package/dist/io/xlsx/write.d.ts +8 -7
  159. package/dist/io/xlsx/write.js +6 -3
  160. package/dist/io/xlsx/x14-ext.d.ts +1 -1
  161. package/dist/sha512.d.ts +2 -0
  162. package/dist/sha512.js +146 -0
  163. package/dist/vba/bytes.d.ts +11 -0
  164. package/dist/vba/bytes.js +29 -0
  165. package/dist/vba/cfb-writer.d.ts +1 -1
  166. package/dist/vba/cfb.d.ts +1 -1
  167. package/dist/vba/cfb.js +15 -51
  168. package/dist/vba/codepage.d.ts +2 -2
  169. package/dist/vba/dir-records.d.ts +53 -0
  170. package/dist/vba/dir-records.js +28 -0
  171. package/dist/vba/errors.d.ts +2 -2
  172. package/dist/vba/index.d.ts +2 -2
  173. package/dist/vba/index.js +2 -2
  174. package/dist/vba/ms-ovba.d.ts +1 -1
  175. package/dist/vba/ms-ovba.js +1 -3
  176. package/dist/vba/project-editor.d.ts +9 -9
  177. package/dist/vba/project-editor.js +6 -44
  178. package/dist/vba/project.d.ts +5 -5
  179. package/dist/vba/project.js +3 -28
  180. package/dist/vba/vba-encoding.d.ts +1 -1
  181. package/dist/xml/errors.d.ts +1 -1
  182. package/dist/xml/xml-read.d.ts +98 -9
  183. package/dist/xml/xml-read.js +70 -0
  184. package/dist/xml/xml.d.ts +74 -10
  185. package/dist/xml/xml.js +39 -7
  186. package/package.json +59 -36
package/README.md CHANGED
@@ -9,6 +9,8 @@
9
9
  A TypeScript-first library for reading and writing spreadsheet documents
10
10
  (`.xlsx` / OOXML, and CSV) — synchronous, `Uint8Array`-native, and dependency-lean.
11
11
 
12
+ **[Documentation, guide and a browser playground](https://shbernal.github.io/ts-xlsx/)**
13
+
12
14
  > **Status — 1.0.0, the first release.**
13
15
  > `ts-xlsx` began as a hard fork of [ExcelJS](https://github.com/exceljs/exceljs)
14
16
  > and has been rebuilt from the ground up into a modern, strict-TypeScript library.
@@ -130,10 +132,12 @@ import {
130
132
  readXlsx, writeXlsx, // buffered .xlsx (Uint8Array ⇄ Workbook)
131
133
  readSheetRows, // stream one sheet's rows, bounded memory
132
134
  readWorkbookStream, // stream every sheet, rows one at a time
133
- WorkbookStreamWriter, // write a workbook incrementally, bounded memory
134
135
  readCsv, writeCsv, // CSV as Uint8Array
135
136
  writeCsvText, // CSV as a string
136
137
  } from '@shbernal/ts-xlsx';
138
+ // The streaming writer opens files and pipes Node streams, so it is the one Node-only face
139
+ // and lives behind its own subpath — which is what keeps everything above browser-safe:
140
+ import {WorkbookStreamWriter} from '@shbernal/ts-xlsx/node';
137
141
 
138
142
  // Bounded-memory extraction — the whole workbook is never materialized:
139
143
  for (const row of readSheetRows(bytes, {sheet: 'People'})) {
@@ -189,18 +193,24 @@ on — a Lambda with no bundler, a service that only classifies failures:
189
193
 
190
194
  | Import from | You get | It loads |
191
195
  | --- | --- | --- |
192
- | `@shbernal/ts-xlsx` | everything | 902 KB |
193
- | `@shbernal/ts-xlsx/core` | `Workbook`, `Worksheet`, `Cell`, styles, values, addresses | 332 KB |
194
- | `@shbernal/ts-xlsx/xlsx` | `readXlsx`, `writeXlsx`/`writeXlsxAsync`, the streaming pair, VBA part edits | 887 KB |
195
- | `@shbernal/ts-xlsx/xlsb` | `readXlsb` | 469 KB |
196
- | `@shbernal/ts-xlsx/csv` | `readCsv`, `writeCsv`, `writeCsvText` | 341 KB |
197
- | `@shbernal/ts-xlsx/vba` | `parseVbaProject`, `addVbaReference`, `removeVbaModule` | 73 KB |
198
- | `@shbernal/ts-xlsx/customui` | `parseCustomUi` and the ribbon types | 26 KB |
199
- | `@shbernal/ts-xlsx/errors` | every error class the library throws | 12 KB |
196
+ | `@shbernal/ts-xlsx` | everything except the streaming writer | 501 KB |
197
+ | `@shbernal/ts-xlsx/core` | `Workbook`, `Worksheet`, `Cell`, styles, values, addresses | 185 KB |
198
+ | `@shbernal/ts-xlsx/xlsx` | `readXlsx`, `writeXlsx`/`writeXlsxAsync`, the streaming reader, VBA part edits | 490 KB |
199
+ | `@shbernal/ts-xlsx/xlsb` | `readXlsb` | 256 KB |
200
+ | `@shbernal/ts-xlsx/csv` | `readCsv`, `writeCsv`, `writeCsvText` | 191 KB |
201
+ | `@shbernal/ts-xlsx/node` | `WorkbookStreamWriter` and the rest of the streaming writer | 358 KB |
202
+ | `@shbernal/ts-xlsx/vba` | `parseVbaProject`, `addVbaReference`, `removeVbaModule` | 46 KB |
203
+ | `@shbernal/ts-xlsx/customui` | `parseCustomUi` and the ribbon types | 15 KB |
204
+ | `@shbernal/ts-xlsx/errors` | every error class the library throws | 3 KB |
205
+
206
+ `/node` is the only one the root specifier does not carry, and the reason is its imports
207
+ rather than its size: it reaches `node:fs` and `node:stream`, and every other entry reaches
208
+ no Node built-in at all, which is what makes them bundle for a browser with nothing to
209
+ configure ([ADR-0040](docs/decisions/0040-the-browser-boundary-is-an-entry-point.md)).
200
210
 
201
211
  Every error class lives in `/errors` and nowhere else, because a container-level failure
202
212
  belongs to no single codec — `readXlsx` and `readXlsb` both raise `UnsupportedFormatError`.
203
- Catching and classifying therefore costs 12 KB, not a parser.
213
+ Catching and classifying therefore costs 3 KB, not a parser.
204
214
 
205
215
  `/xlsx` is barely cheaper than the whole package, and that is honest rather than a defect:
206
216
  `readXlsx` sniffs the bytes and hands a binary package to the BIFF12 reader, so the `.xlsb`
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Join byte chunks into one buffer.
3
+ *
4
+ * Both parameters exist to avoid work the caller has already done. `size` is the total length when
5
+ * the caller knows it, as a reader draining a length-prefixed stream does, which skips a pass over
6
+ * the chunks. A lone chunk is handed straight back rather than copied, which is the common case on
7
+ * the inflate path, where a part that fits in one chunk would otherwise be duplicated in memory the
8
+ * moment it is read.
9
+ *
10
+ * The returned buffer therefore aliases the caller's chunk when there is exactly one. Every caller
11
+ * here is a reader assembling bytes it then only reads, so this is sound; a caller that means to
12
+ * mutate the result must copy it.
13
+ */
14
+ export declare function concat(chunks: readonly Uint8Array[], size?: number): Uint8Array;
15
+ /**
16
+ * Standard base64 of `bytes`, padded with `=`.
17
+ *
18
+ * Spelled out rather than delegated because both platform routes are unavailable here: `Buffer` is
19
+ * a Node global, which is exactly what a browser-safe module may not reach for, and `btoa` is
20
+ * declared deprecated in Node's types (so the `no-deprecated` lint rule rejects it) and takes a
21
+ * binary string rather than bytes anyway. The one caller encodes a 16-byte salt and a 64-byte
22
+ * hash, so the loop below is not on any path where its cost is measurable.
23
+ */
24
+ export declare function toBase64(bytes: Uint8Array): string;
package/dist/bytes.js ADDED
@@ -0,0 +1,34 @@
1
+ export function concat(chunks, size) {
2
+ const first = chunks[0];
3
+ if (chunks.length === 1 && first !== undefined)
4
+ return first;
5
+ let total = size;
6
+ if (total === undefined) {
7
+ total = 0;
8
+ for (const chunk of chunks)
9
+ total += chunk.length;
10
+ }
11
+ const out = new Uint8Array(total);
12
+ let offset = 0;
13
+ for (const chunk of chunks) {
14
+ out.set(chunk, offset);
15
+ offset += chunk.length;
16
+ }
17
+ return out;
18
+ }
19
+ const BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
20
+ export function toBase64(bytes) {
21
+ let out = '';
22
+ for (let i = 0; i < bytes.length; i += 3) {
23
+ const a = bytes[i] ?? 0;
24
+ const b = bytes[i + 1] ?? 0;
25
+ const c = bytes[i + 2] ?? 0;
26
+ const triple = (a << 16) | (b << 8) | c;
27
+ const remaining = bytes.length - i;
28
+ out += BASE64_ALPHABET.charAt((triple >>> 18) & 63);
29
+ out += BASE64_ALPHABET.charAt((triple >>> 12) & 63);
30
+ out += remaining > 1 ? BASE64_ALPHABET.charAt((triple >>> 6) & 63) : '=';
31
+ out += remaining > 2 ? BASE64_ALPHABET.charAt(triple & 63) : '=';
32
+ }
33
+ return out;
34
+ }
@@ -2,9 +2,33 @@
2
2
  export declare const MAX_COLUMN = 16384;
3
3
  /** Excel's row bound: 1 through 1048576. The other axis of {@link MAX_COLUMN}. */
4
4
  export declare const MAX_ROW = 1048576;
5
+ /** @throws {RangeError} unless `n` is an integer in `1..MAX_COLUMN`. */
6
+ export declare function assertColumnInBounds(n: number): void;
7
+ /** @throws {RangeError} unless `n` is an integer in `1..MAX_ROW`. */
8
+ export declare function assertRowInBounds(n: number): void;
9
+ /**
10
+ * A rectangular block of the grid, as **inclusive** 1-based bounds on both axes.
11
+ *
12
+ * One declaration because inclusive-first/last is the convention every range-shaped thing in this
13
+ * library follows, and three copies of a convention are three places it can drift. A merged region,
14
+ * a table's extent and a {@link Range} handle are all this shape; what differs between them is what
15
+ * the rectangle *means*, which is what their own names carry.
16
+ */
17
+ export interface GridRect {
18
+ /** 1-based row of the top edge. */
19
+ readonly top: number;
20
+ /** 1-based column of the left edge. */
21
+ readonly left: number;
22
+ /** 1-based row of the bottom edge, inclusive. */
23
+ readonly bottom: number;
24
+ /** 1-based column of the right edge, inclusive. */
25
+ readonly right: number;
26
+ }
27
+ /** Whether two grid rectangles share at least one cell. */
28
+ export declare function rectsOverlap(a: GridRect, b: GridRect): boolean;
5
29
  /** A decoded single-cell reference. An axis the reference omits is `undefined`. */
6
30
  export interface CellAddress {
7
- /** Canonical A1 form with `$` anchors stripped e.g. `"B2"`, `"1"`, `"A"`. */
31
+ /** Canonical A1 form with `$` anchors stripped: e.g. `"B2"`, `"1"`, `"A"`. */
8
32
  readonly address: string;
9
33
  /** 1-based column, or `undefined` for a row-only reference (`$1`). */
10
34
  readonly col: number | undefined;
@@ -25,13 +49,24 @@ export interface RangeAddress {
25
49
  readonly sheetName?: string;
26
50
  readonly tl: CellAddress;
27
51
  readonly br: CellAddress;
28
- /** Canonical `tl:br` form `"A1:B2"`, `"1:1"` (rows), `"A:A"` (columns). */
52
+ /** Canonical `tl:br` form: `"A1:B2"`, `"1:1"` (rows), `"A:A"` (columns). */
29
53
  readonly dimensions: string;
30
54
  }
31
- /** Convert a 1-based column number to its letters (`1 → "A"`, `27 → "AA"`). */
55
+ /** Convert a 1-based column number to its letters (`1 → "A"`, `27 → "AA"`).
56
+ *
57
+ * @throws {RangeError} unless `n` is an integer in `1..MAX_COLUMN`. */
32
58
  export declare function numberToColumn(n: number): string;
33
59
  /** Convert column letters to a 1-based number (`"A" → 1`, `"AA" → 27`). */
34
60
  export declare function columnToNumber(letters: string): number;
61
+ /**
62
+ * One corner of a range, as its reference text. An axis the corner omits contributes nothing, so a
63
+ * whole-row corner spells `6` and a whole-column one spells `B`, rather than acquiring the other
64
+ * axis from a sentinel. That is the honesty rule this module's header states, and letting an absent
65
+ * axis decay into text is exactly how `"$undefined$1"` and `"NaN:NaN"` used to reach a file.
66
+ *
67
+ * The unbounded counterpart to {@link encodeAddress}, which names one cell and so requires both.
68
+ */
69
+ export declare function encodeCornerRef(col: number | undefined, row: number | undefined): string;
35
70
  /**
36
71
  * Decode a single cell/row/column reference into `{address, col, row}`. Anchoring
37
72
  * `$` signs are accepted and dropped; an absent axis is `undefined`.
@@ -39,6 +74,40 @@ export declare function columnToNumber(letters: string): number;
39
74
  * @throws {SyntaxError} if the reference mentions neither a column nor a row.
40
75
  */
41
76
  export declare function decodeAddress(reference: string): CellAddress;
77
+ /**
78
+ * A reference that names one cell, both axes present. The narrowing of {@link CellAddress} that
79
+ * most callers actually want: `decodeAddress` is deliberately three-shaped because a bare row
80
+ * (`$1`) and a bare column (`$A`) are legitimate references, but a cell is where a value lives, and
81
+ * every caller that needs one was re-deriving that invariant by hand.
82
+ */
83
+ export interface CellPosition {
84
+ readonly col: number;
85
+ readonly row: number;
86
+ }
87
+ /**
88
+ * Decode a reference that must name a single cell. Anchoring `$` signs are accepted and dropped.
89
+ *
90
+ * @throws {SyntaxError} if the reference is unparseable, or parses but omits an axis (`"A"`, `"1"`).
91
+ */
92
+ export declare function decodeCellRef(reference: string): CellPosition;
93
+ /**
94
+ * {@link decodeCellRef} for a reference that came out of a file rather than out of a caller:
95
+ * `undefined` for anything that does not name one cell that can exist: a range, a bare row or
96
+ * column, a position off the grid (`A0`, `XFE1`), or outright garbage. A foreign producer writes
97
+ * all of them, and none is worth throwing over when the reading code's answer is simply "then
98
+ * there is nothing here".
99
+ */
100
+ export declare function tryDecodeCellRef(reference: string): CellPosition | undefined;
101
+ /**
102
+ * {@link decodeRange} for a reference that came out of a file: `undefined` for anything that does
103
+ * not name a region that can exist. The sibling of {@link tryDecodeCellRef} on the other arity: a
104
+ * `ref` or one area of a `sqref` is as likely to be malformed as a cell's `r`, and the reader's
105
+ * answer to both is the same one.
106
+ *
107
+ * An axis neither endpoint mentions stays `undefined`, exactly as in `decodeRange`: a whole-column
108
+ * range is unbounded, not unreadable, and a caller that needs a bounded rectangle says so itself.
109
+ */
110
+ export declare function tryDecodeRange(reference: string): RangeAddress | undefined;
42
111
  /**
43
112
  * Decode a range reference (`A1:B2`, `$1:$1`, `Sheet1!$A:$A`) into its corners and
44
113
  * canonical dimensions. A single reference collapses to a degenerate range whose
@@ -1,11 +1,22 @@
1
1
  export const MAX_COLUMN = 16384;
2
2
  export const MAX_ROW = 1048576;
3
+ export function assertColumnInBounds(n) {
4
+ if (!Number.isInteger(n) || n < 1 || n > MAX_COLUMN) {
5
+ throw new RangeError(`column ${n} is out of bounds: Excel supports 1..${MAX_COLUMN}`);
6
+ }
7
+ }
8
+ export function assertRowInBounds(n) {
9
+ if (!Number.isInteger(n) || n < 1 || n > MAX_ROW) {
10
+ throw new RangeError(`row ${n} is out of bounds: Excel supports 1..${MAX_ROW}`);
11
+ }
12
+ }
13
+ export function rectsOverlap(a, b) {
14
+ return a.left <= b.right && b.left <= a.right && a.top <= b.bottom && b.top <= a.bottom;
15
+ }
3
16
  const SINGLE_REF = /^\$?([A-Z]*)\$?(\d*)$/;
4
17
  const SHEET_PREFIX = /^(?:(?:'((?:[^']|'')*)')|([^'!]+))!(.*)$/;
5
18
  export function numberToColumn(n) {
6
- if (!Number.isInteger(n) || n < 1 || n > MAX_COLUMN) {
7
- throw new RangeError(`column ${n} is out of bounds — Excel supports 1..${MAX_COLUMN}`);
8
- }
19
+ assertColumnInBounds(n);
9
20
  let letters = '';
10
21
  let remaining = n;
11
22
  while (remaining > 0) {
@@ -28,16 +39,15 @@ export function columnToNumber(letters) {
28
39
  n = n * 26 + (code - 64);
29
40
  }
30
41
  if (n > MAX_COLUMN) {
31
- throw new RangeError(`column "${letters}" is out of bounds Excel supports up to ${MAX_COLUMN} (XFD)`);
42
+ throw new RangeError(`column "${letters}" is out of bounds: Excel supports up to ${MAX_COLUMN} (XFD)`);
32
43
  }
33
44
  return n;
34
45
  }
46
+ export function encodeCornerRef(col, row) {
47
+ return `${col !== undefined ? numberToColumn(col) : ''}${row !== undefined ? row : ''}`;
48
+ }
35
49
  function makeCellAddress(col, row) {
36
- return {
37
- address: `${col !== undefined ? numberToColumn(col) : ''}${row !== undefined ? row : ''}`,
38
- col,
39
- row,
40
- };
50
+ return { address: encodeCornerRef(col, row), col, row };
41
51
  }
42
52
  export function decodeAddress(reference) {
43
53
  const match = SINGLE_REF.exec(reference);
@@ -53,6 +63,36 @@ export function decodeAddress(reference) {
53
63
  const row = digits.length > 0 ? Number.parseInt(digits, 10) : undefined;
54
64
  return { address: `${letters}${digits}`, col, row };
55
65
  }
66
+ export function decodeCellRef(reference) {
67
+ const { col, row } = decodeAddress(reference);
68
+ if (col === undefined || row === undefined) {
69
+ throw new SyntaxError(`"${reference}" is not a single-cell reference: it omits a column or row`);
70
+ }
71
+ return { col, row };
72
+ }
73
+ function rowCanExist(row) {
74
+ return row === undefined || (Number.isInteger(row) && row >= 1 && row <= MAX_ROW);
75
+ }
76
+ export function tryDecodeCellRef(reference) {
77
+ let position;
78
+ try {
79
+ position = decodeCellRef(reference);
80
+ }
81
+ catch {
82
+ return undefined;
83
+ }
84
+ return rowCanExist(position.row) ? position : undefined;
85
+ }
86
+ export function tryDecodeRange(reference) {
87
+ let range;
88
+ try {
89
+ range = decodeRange(reference);
90
+ }
91
+ catch {
92
+ return undefined;
93
+ }
94
+ return rowCanExist(range.top) && rowCanExist(range.bottom) ? range : undefined;
95
+ }
56
96
  export function decodeRange(reference) {
57
97
  const prefix = SHEET_PREFIX.exec(reference);
58
98
  let sheetName;
@@ -86,7 +126,7 @@ export function decodeRange(reference) {
86
126
  }
87
127
  export function encodeAddress(col, row) {
88
128
  if (!Number.isInteger(row) || row < 1) {
89
- throw new RangeError(`row ${row} is out of bounds rows start at 1`);
129
+ throw new RangeError(`row ${row} is out of bounds: rows start at 1`);
90
130
  }
91
131
  return `${numberToColumn(col)}${row}`;
92
132
  }
@@ -18,7 +18,7 @@ export interface FilterColumn {
18
18
  /** The two criteria kinds this library models: a discrete value set, or operator predicates. */
19
19
  export type FilterCriteria = ValuesFilter | CustomFilter;
20
20
  /**
21
- * A column narrowed to a discrete set of allowed values the checkbox list in Excel's dropdown.
21
+ * A column narrowed to a discrete set of allowed values: the checkbox list in Excel's dropdown.
22
22
  * A row survives when its cell in this column matches one of {@link values} (or is blank, when
23
23
  * {@link blank} is set).
24
24
  */
@@ -48,8 +48,19 @@ export declare function isCustomFilterOperator(value: string): value is CustomFi
48
48
  * Validate and normalise a settable autofilter into its canonical stored form. A bare range string
49
49
  * is the common case (dropdowns, no criteria); an {@link AutoFilter} object carries per-column
50
50
  * criteria too. Throws when the range is not a bounded rectangle, or a column's `colId` falls
51
- * outside it, or a custom filter does not carry one or two predicates a filter authored wrong is
51
+ * outside it, or a custom filter does not carry one or two predicates: a filter authored wrong is
52
52
  * a bug to surface, not to silently repair. (The reader sanitises hostile input before it reaches
53
53
  * here, so load never trips these guards.)
54
54
  */
55
55
  export declare function canonicalizeAutoFilter(input: string | AutoFilter): AutoFilter;
56
+ /**
57
+ * Re-anchor a filter through a splice of `count` lines at `start` on `axis`, or drop it (`undefined`)
58
+ * when the splice deleted every line it covered.
59
+ *
60
+ * A row splice only moves the range. A column splice moves its left edge too, and a criterion is
61
+ * addressed by its offset from that edge rather than by an absolute column, so every offset is
62
+ * re-measured against the new edge and a criterion whose column was deleted goes with the column.
63
+ * Left alone, those offsets would keep their old numbers and silently re-point each filter at a
64
+ * neighbouring column.
65
+ */
66
+ export declare function shiftAutoFilter(filter: AutoFilter, axis: 'row' | 'col', start: number, count: number, delta: number): AutoFilter | undefined;
@@ -1,15 +1,16 @@
1
1
  import { AuthoringError } from '../errors.js';
2
- import { decodeRange } from './address.js';
3
- const CUSTOM_FILTER_OPERATORS = new Set([
4
- 'equal',
5
- 'notEqual',
6
- 'lessThan',
7
- 'lessThanOrEqual',
8
- 'greaterThan',
9
- 'greaterThanOrEqual',
10
- ]);
2
+ import { decodeRange, encodeAddress } from './address.js';
3
+ import { isDeletedSpan, shiftIndex } from './grid-shift.js';
4
+ const CUSTOM_FILTER_OPERATORS = {
5
+ equal: true,
6
+ notEqual: true,
7
+ lessThan: true,
8
+ lessThanOrEqual: true,
9
+ greaterThan: true,
10
+ greaterThanOrEqual: true,
11
+ };
11
12
  export function isCustomFilterOperator(value) {
12
- return CUSTOM_FILTER_OPERATORS.has(value);
13
+ return Object.hasOwn(CUSTOM_FILTER_OPERATORS, value);
13
14
  }
14
15
  export function canonicalizeAutoFilter(input) {
15
16
  const ref = typeof input === 'string' ? input : input.ref;
@@ -37,3 +38,27 @@ function canonicalizeColumn(column, width) {
37
38
  }
38
39
  return column;
39
40
  }
41
+ export function shiftAutoFilter(filter, axis, start, count, delta) {
42
+ const { top, left, bottom, right } = decodeRange(filter.ref);
43
+ if (top === undefined || left === undefined || bottom === undefined || right === undefined) {
44
+ return filter;
45
+ }
46
+ const [lo, hi] = axis === 'row' ? [top, bottom] : [left, right];
47
+ if (isDeletedSpan(lo, hi, start, count))
48
+ return undefined;
49
+ const movedLo = shiftIndex(lo, start, count, delta, axis);
50
+ const movedHi = shiftIndex(hi, start, count, delta, axis);
51
+ if (axis === 'row') {
52
+ const ref = `${encodeAddress(left, movedLo)}:${encodeAddress(right, movedHi)}`;
53
+ return { ref, columns: filter.columns };
54
+ }
55
+ const ref = `${encodeAddress(movedLo, top)}:${encodeAddress(movedHi, bottom)}`;
56
+ const columns = [];
57
+ for (const column of filter.columns) {
58
+ const absolute = left + column.colId;
59
+ if (isDeletedSpan(absolute, absolute, start, count))
60
+ continue;
61
+ columns.push({ ...column, colId: shiftIndex(absolute, start, count, delta, axis) - movedLo });
62
+ }
63
+ return { ref, columns };
64
+ }
@@ -3,8 +3,8 @@ import { type Alignment, type Border, type CellStyle, type Fill, type Font, type
3
3
  import { type CellValue, type RichTextRun, type ValueType } from './value.ts';
4
4
  import type { CellModel } from './worksheet.ts';
5
5
  /**
6
- * A single cell owns its value and every style facet outright. Each facet below fill, number format,
7
- * font, border, alignment, protection, quote-prefix, and note is held in the cell's own field and
6
+ * A single cell owns its value and every style facet outright. Each facet below (fill, number format,
7
+ * font, border, alignment, protection, quote-prefix, and note) is held in the cell's own field and
8
8
  * *replaced* (never mutated in place) by its setter, so a facet set on one cell never aliases or bleeds
9
9
  * onto its row, column, or sheet siblings. Each facet's own doc covers only what is specific to it.
10
10
  */
@@ -14,6 +14,7 @@ export declare class Cell {
14
14
  readonly row: number;
15
15
  /** 1-based column index. */
16
16
  readonly col: number;
17
+ /** @throws {RangeError} unless both axes are integers within Excel's grid. */
17
18
  constructor(row: number, col: number);
18
19
  /** Canonical A1 address of this cell (`"B3"`). */
19
20
  get address(): string;
@@ -23,12 +24,12 @@ export declare class Cell {
23
24
  /** The observable {@link ValueType} of the current value. */
24
25
  get type(): ValueType;
25
26
  /**
26
- * The cell's value as plain text ({@link cellValueToText}), `""` when it is empty so a reader
27
+ * The cell's value as plain text ({@link cellValueToText}), `""` when it is empty, so a reader
27
28
  * that only wants strings never has to narrow the value union itself.
28
29
  *
29
30
  * Read-only, because text is a *rendering* of the value and not a second place to store one:
30
31
  * writing `"3"` here could only mean the string `"3"`, which is exactly `value = '3'` and reads
31
- * nothing like it. The number format is not applied either the style is not the cell's value,
32
+ * nothing like it. The number format is not applied either: the style is not the cell's value,
32
33
  * so a currency cell's text carries no currency sign.
33
34
  */
34
35
  get text(): string;
@@ -39,27 +40,27 @@ export declare class Cell {
39
40
  *
40
41
  * This exists because a run's format element does **not** inherit anything. A `<rPr>` is a
41
42
  * *complete* character format, and any facet it omits falls back to the workbook default font
42
- * ({@link Workbook.defaultFont}) not to the cell's. Verified against Excel: a cell set to
43
+ * ({@link Workbook.defaultFont}), not to the cell's. Verified against Excel: a cell set to
43
44
  * Courier New 16 whose first run carries only `<b/>` renders that run in the workbook default face
44
45
  * at the default size, bold, while the rest of the cell renders Courier New 16. So a run authored
45
46
  * as `{bold: true}` beside a styled cell silently loses the face, which is the format's rule rather
46
- * than a bug and the reason this is a helper rather than a change to how runs are written.
47
+ * than a bug, and the reason this is a helper rather than a change to how runs are written.
47
48
  *
48
49
  * Composition is per facet: a facet the run names wins, one it omits comes from the cell. Assigning
49
50
  * `value` directly stays the bare path, for a caller who wants a run that deliberately falls back
50
51
  * to the workbook default.
51
52
  *
52
- * A cell that names no font of its own needs no composition an omitted facet already falls back
53
- * to the workbook default, which is exactly what such a cell renders in so the runs pass through
53
+ * A cell that names no font of its own needs no composition: an omitted facet already falls back
54
+ * to the workbook default, which is exactly what such a cell renders in, so the runs pass through
54
55
  * unchanged.
55
56
  */
56
57
  setRichText(runs: readonly RichTextRun[]): void;
57
58
  /**
58
- * The cell's full style fill, number format, font, border, alignment, and protection as one
59
+ * The cell's full style (fill, number format, font, border, alignment, and protection) as one
59
60
  * {@link CellStyle}, for restyling a cell wholesale without importing {@link applyCellStyle}
60
61
  * separately (mirrors {@link Worksheet.model}'s getter/setter pair for the whole sheet). The
61
62
  * getter carries only the facets this cell has set (the same shape {@link cellToModel} emits);
62
- * the setter lays each facet `style` carries onto this cell like every per-facet setter, it
63
+ * the setter lays each facet `style` carries onto this cell and, like every per-facet setter, it
63
64
  * replaces that facet outright but leaves a facet `style` omits untouched, so `cell.style = {...}`
64
65
  * composes with prior per-facet sets rather than clearing them wholesale.
65
66
  */
@@ -71,35 +72,35 @@ export declare class Cell {
71
72
  /**
72
73
  * The cell's number-format code (`"0.00%"`, a custom accounting format, …), or
73
74
  * `undefined` for the General format. Stored verbatim: the invariant form Excel
74
- * persists `.` decimal, `,` grouping, `/` date separator is neither localized
75
+ * persists (`.` decimal, `,` grouping, `/` date separator) is neither localized
75
76
  * nor rewritten, so the code round-trips character-for-character. A cell that also carries
76
77
  * a column-level format keeps both, so overriding one facet never drops the other.
77
78
  */
78
79
  get numFmt(): string | undefined;
79
80
  set numFmt(numFmt: string | undefined);
80
81
  /**
81
- * The cell's font bold/italic/underline, size, colour, typeface as a partial set
82
+ * The cell's font (bold/italic/underline, size, colour, typeface) as a partial set
82
83
  * of the facets that differ from the default (only the facets actually set are carried,
83
84
  * exactly as OOXML stores them). `undefined` means the cell uses the workbook default font.
84
85
  */
85
86
  get font(): Font | undefined;
86
87
  set font(font: Font | undefined);
87
88
  /**
88
- * The cell's border the line style and colour of each side or `undefined` when the
89
+ * The cell's border (the line style and colour of each side), or `undefined` when the
89
90
  * cell has none. An absent edge within a border means that side is unbordered, so reading
90
91
  * a cell never fabricates a border it does not have.
91
92
  */
92
93
  get border(): Border | undefined;
93
94
  set border(border: Border | undefined);
94
95
  /**
95
- * The cell's alignment how its content sits within the cell, plus the wrap/shrink flags
96
+ * The cell's alignment (how its content sits within the cell, plus the wrap/shrink flags),
96
97
  * or `undefined` when it uses the defaults. The boolean flags are off unless explicitly set,
97
98
  * so a cell that never enabled wrapping never reads back wrapped.
98
99
  */
99
100
  get alignment(): Alignment | undefined;
100
101
  set alignment(alignment: Alignment | undefined);
101
102
  /**
102
- * The cell's protection its locked/hidden flags, enforced only once the sheet is protected
103
+ * The cell's protection (its locked/hidden flags, enforced only once the sheet is protected),
103
104
  * or `undefined` when the cell carries neither. `locked` defaults to on in OOXML, so a cell
104
105
  * that never touched protection is implicitly locked and reads back as `undefined`, not as
105
106
  * `{locked: true}`; the flag only becomes explicit when a cell is unlocked.
@@ -110,7 +111,7 @@ export declare class Cell {
110
111
  * The quote-prefix flag: when set, a spreadsheet stores the cell's content as literal text even
111
112
  * when it looks like a formula or number, and shows a leading apostrophe in the formula bar without
112
113
  * that apostrophe being part of the stored value. `undefined` (or `false`) when unset. It is a
113
- * cell-format flag an attribute on the cell's `xf` record so it composes independently of the
114
+ * cell-format flag, an attribute on the cell's `xf` record, so it composes independently of the
114
115
  * value.
115
116
  */
116
117
  get quotePrefix(): boolean | undefined;
package/dist/core/cell.js CHANGED
@@ -1,4 +1,4 @@
1
- import { encodeAddress } from './address.js';
1
+ import { assertColumnInBounds, assertRowInBounds, encodeAddress } from './address.js';
2
2
  import { NAMED_STYLE_ID } from './internal.js';
3
3
  import { assignStyleFacets, } from './style.js';
4
4
  import { cellValueToText, coerceCellValue, detectValueType, } from './value.js';
@@ -16,12 +16,8 @@ export class Cell {
16
16
  #namedStyleId;
17
17
  #note;
18
18
  constructor(row, col) {
19
- if (!Number.isInteger(row) || row < 1) {
20
- throw new RangeError(`cell row ${row} is out of bounds — rows start at 1`);
21
- }
22
- if (!Number.isInteger(col) || col < 1) {
23
- throw new RangeError(`cell column ${col} is out of bounds — columns start at 1`);
24
- }
19
+ assertRowInBounds(row);
20
+ assertColumnInBounds(col);
25
21
  this.row = row;
26
22
  this.col = col;
27
23
  }
@@ -1,21 +1,21 @@
1
- import type { Color } from './style.ts';
1
+ import { type Color } from './style.ts';
2
2
  import { type ThemeColorScheme } from './theme.ts';
3
3
  /**
4
- * The built-in indexed colour palette (ECMA-376 §18.8.27), by index. Entries 0–7 duplicate 8–15
4
+ * The built-in indexed colour palette (ECMA-376 §18.8.27), by index. Entries 0–7 duplicate 8–15, a
5
5
  * redundancy the spec preserves for backwards compatibility with the legacy formats this palette came
6
- * from and the table is only 64 long: indices 64 and 65 are the *system* foreground and background,
6
+ * from, and the table is only 64 long: indices 64 and 65 are the *system* foreground and background,
7
7
  * which name whatever the operating system's window colours are and therefore have no fixed value at
8
8
  * all (see {@link SYSTEM_INDEXED_COLORS}).
9
9
  *
10
- * The spec writes each entry with a leading `00`. That byte is not an alpha channel a palette of
11
- * fully transparent colours would be absurd it is an artefact of the 32-bit colour records these
10
+ * The spec writes each entry with a leading `00`. That byte is not an alpha channel, since a palette
11
+ * of fully transparent colours would be absurd. It is an artefact of the 32-bit colour records these
12
12
  * values were lifted from, which is why {@link resolveColor} returns them fully opaque.
13
13
  */
14
14
  export declare const DEFAULT_INDEXED_COLORS: readonly string[];
15
15
  /**
16
16
  * The two indices that are not colours: 64 is the system foreground and 65 the system background.
17
17
  * They resolve to whatever the viewing system's window colours are, so this library reports them
18
- * unresolved rather than inventing black and white a caller that wants to paint them must decide
18
+ * unresolved rather than inventing black and white: a caller that wants to paint them must decide
19
19
  * for itself what "automatic" means in its context. `indexed="64"` in particular is extremely common:
20
20
  * it is the placeholder every solid fill Excel writes carries as its background colour.
21
21
  */
@@ -27,14 +27,14 @@ export interface ColorResolutionContext {
27
27
  /**
28
28
  * The workbook's custom indexed palette, by index, each entry an ARGB string. Empty or absent means
29
29
  * the workbook rides {@link DEFAULT_INDEXED_COLORS}. A custom palette replaces the built-in one
30
- * wholesale that is what `<indexedColors>` means so a short custom palette leaves the indices
30
+ * wholesale, which is what `<indexedColors>` means, so a short custom palette leaves the indices
31
31
  * past its end unresolved rather than falling through to the built-in entry.
32
32
  */
33
33
  readonly indexed?: readonly string[] | undefined;
34
34
  }
35
35
  /**
36
36
  * Resolve a colour reference to a concrete 8-hex ARGB string, or `undefined` when it cannot be
37
- * resolved an `auto` colour, a system indexed colour, a theme slot the workbook's scheme does not
37
+ * resolved: an `auto` colour, a system indexed colour, a theme slot the workbook's scheme does not
38
38
  * declare, or an index past the end of a custom palette.
39
39
  *
40
40
  * Precedence follows what the encodings mean: an explicit `argb` is already concrete and wins; then
@@ -1,3 +1,4 @@
1
+ import { parseArgb } from './style.js';
1
2
  import { DEFAULT_THEME_COLOR_SCHEME, THEME_COLOR_SLOTS } from './theme.js';
2
3
  export const DEFAULT_INDEXED_COLORS = [
3
4
  '00000000', '00FFFFFF', '00FF0000', '0000FF00', '000000FF', '00FFFF00', '00FF00FF', '0000FFFF',
@@ -19,9 +20,12 @@ export function resolveColor(color, context = {}) {
19
20
  return base;
20
21
  return applyTint(base, tint);
21
22
  }
23
+ function resolveArgb(value) {
24
+ return parseArgb(value)?.toUpperCase();
25
+ }
22
26
  function resolveBase(color, context) {
23
27
  if (color.argb !== undefined)
24
- return normalizeArgb(color.argb);
28
+ return resolveArgb(color.argb);
25
29
  if (color.theme !== undefined) {
26
30
  const slot = THEME_COLOR_SLOTS[color.theme];
27
31
  if (slot === undefined)
@@ -38,16 +42,11 @@ function resolveBase(color, context) {
38
42
  : DEFAULT_INDEXED_COLORS[color.indexed];
39
43
  if (entry === undefined)
40
44
  return undefined;
41
- const normalized = normalizeArgb(entry);
42
- return normalized === undefined ? undefined : `FF${normalized.slice(2)}`;
45
+ const parsed = resolveArgb(entry);
46
+ return parsed === undefined ? undefined : `FF${parsed.slice(2)}`;
43
47
  }
44
48
  return undefined;
45
49
  }
46
- function normalizeArgb(value) {
47
- const hex = value.startsWith('#') ? value.slice(1) : value;
48
- const argb = hex.length === 6 ? `FF${hex}` : hex;
49
- return /^[0-9a-fA-F]{8}$/.test(argb) ? argb.toUpperCase() : undefined;
50
- }
51
50
  export function applyTint(argb, tint) {
52
51
  const alpha = argb.slice(0, 2);
53
52
  const r = Number.parseInt(argb.slice(2, 4), 16) / 255;