@shbernal/ts-xlsx 2.0.0 → 3.0.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.
- package/README.md +20 -10
- package/dist/bytes.d.ts +24 -0
- package/dist/bytes.js +34 -0
- package/dist/core/address.d.ts +26 -4
- package/dist/core/address.js +21 -9
- package/dist/core/autofilter.d.ts +12 -1
- package/dist/core/autofilter.js +35 -12
- package/dist/core/axis-handle.d.ts +8 -0
- package/dist/core/axis-handle.js +14 -0
- package/dist/core/cell.d.ts +24 -0
- package/dist/core/cell.js +9 -1
- package/dist/core/color-resolution.d.ts +1 -1
- package/dist/core/color-resolution.js +7 -8
- package/dist/core/column.d.ts +4 -1
- package/dist/core/column.js +31 -35
- package/dist/core/conditional-formatting-overlay.d.ts +5 -0
- package/dist/core/conditional-formatting-overlay.js +11 -0
- package/dist/core/conditional-formatting.d.ts +45 -5
- package/dist/core/conditional-formatting.js +74 -0
- package/dist/core/data-validation-overlay.d.ts +11 -0
- package/dist/core/data-validation-overlay.js +21 -2
- package/dist/core/data-validation.d.ts +9 -2
- package/dist/core/data-validation.js +26 -0
- package/dist/core/grid-edits.d.ts +16 -0
- package/dist/core/grid-edits.js +26 -8
- package/dist/core/grid-shift.d.ts +19 -0
- package/dist/core/grid-shift.js +8 -0
- package/dist/core/image.d.ts +2 -0
- package/dist/core/image.js +2 -0
- package/dist/core/merge-index.d.ts +31 -0
- package/dist/core/merge-index.js +60 -0
- package/dist/core/merge.d.ts +12 -11
- package/dist/core/merge.js +36 -10
- package/dist/core/page-setup.d.ts +86 -2
- package/dist/core/page-setup.js +34 -1
- package/dist/core/pivot-table.d.ts +2 -0
- package/dist/core/pivot-table.js +23 -18
- package/dist/core/protection.d.ts +3 -0
- package/dist/core/protection.js +20 -14
- package/dist/core/row-input.d.ts +7 -0
- package/dist/core/row-input.js +10 -8
- package/dist/core/row.d.ts +4 -1
- package/dist/core/row.js +19 -23
- package/dist/core/style.d.ts +78 -7
- package/dist/core/style.js +90 -80
- package/dist/core/table.d.ts +26 -12
- package/dist/core/table.js +67 -33
- package/dist/core/theme.d.ts +3 -1
- package/dist/core/theme.js +4 -0
- package/dist/core/used-extent.d.ts +44 -0
- package/dist/core/used-extent.js +102 -0
- package/dist/core/workbook.d.ts +2 -2
- package/dist/core/worksheet-comments.d.ts +8 -0
- package/dist/core/worksheet-comments.js +18 -0
- package/dist/core/worksheet.d.ts +31 -7
- package/dist/core/worksheet.js +72 -86
- package/dist/customui/ribbon.js +23 -24
- package/dist/entries/core.d.ts +3 -3
- package/dist/entries/csv.d.ts +1 -1
- package/dist/entries/node-unavailable.d.ts +15 -0
- package/dist/entries/node-unavailable.js +20 -0
- package/dist/entries/node.d.ts +1 -0
- package/dist/entries/node.js +1 -0
- package/dist/entries/xlsx.d.ts +0 -1
- package/dist/entries/xlsx.js +0 -1
- package/dist/io/csv/read.js +1 -1
- package/dist/io/csv/write.d.ts +12 -2
- package/dist/io/csv/write.js +17 -2
- package/dist/io/opc/inflate.js +3 -13
- package/dist/io/opc/part-paths.d.ts +1 -0
- package/dist/io/opc/part-paths.js +15 -0
- package/dist/io/opc/read-opc.d.ts +22 -3
- package/dist/io/opc/read-opc.js +11 -39
- package/dist/io/opc/rels.d.ts +12 -6
- package/dist/io/opc/rels.js +3 -5
- package/dist/io/style/xf-style.d.ts +33 -4
- package/dist/io/style/xf-style.js +12 -1
- package/dist/io/xlsb/read-styles.js +6 -3
- package/dist/io/xlsb/read.d.ts +1 -1
- package/dist/io/xlsb/read.js +5 -8
- package/dist/io/xlsx/cell-accumulator.d.ts +2 -4
- package/dist/io/xlsx/cell-accumulator.js +20 -58
- package/dist/io/xlsx/cell-value.d.ts +1 -1
- package/dist/io/xlsx/cell-value.js +7 -4
- package/dist/io/xlsx/color-xml.d.ts +1 -1
- package/dist/io/xlsx/color-xml.js +8 -7
- package/dist/io/xlsx/comments.js +6 -3
- package/dist/io/xlsx/conditional-formatting.d.ts +4 -3
- package/dist/io/xlsx/conditional-formatting.js +81 -65
- package/dist/io/xlsx/data-validation.d.ts +10 -8
- package/dist/io/xlsx/data-validation.js +44 -41
- package/dist/io/xlsx/edit-vba.js +4 -11
- package/dist/io/xlsx/hyperlinks.d.ts +4 -2
- package/dist/io/xlsx/hyperlinks.js +26 -31
- package/dist/io/xlsx/images.js +33 -35
- package/dist/io/xlsx/package-plan.d.ts +9 -1
- package/dist/io/xlsx/package-plan.js +29 -16
- package/dist/io/xlsx/part-names.d.ts +41 -0
- package/dist/io/xlsx/part-names.js +46 -0
- package/dist/io/xlsx/read-pivot.js +4 -9
- package/dist/io/xlsx/read-rows.js +12 -13
- package/dist/io/xlsx/read-shared-strings.js +9 -47
- package/dist/io/xlsx/read-styles.d.ts +1 -1
- package/dist/io/xlsx/read-styles.js +65 -43
- package/dist/io/xlsx/read-worksheet.d.ts +10 -1
- package/dist/io/xlsx/read-worksheet.js +48 -63
- package/dist/io/xlsx/read.d.ts +3 -4
- package/dist/io/xlsx/read.js +124 -146
- package/dist/io/xlsx/rich-runs.d.ts +41 -7
- package/dist/io/xlsx/rich-runs.js +95 -30
- package/dist/io/xlsx/sheet-properties.d.ts +1 -1
- package/dist/io/xlsx/sheet-properties.js +23 -26
- package/dist/io/xlsx/styles.d.ts +3 -15
- package/dist/io/xlsx/styles.js +83 -105
- package/dist/io/xlsx/tables.js +14 -13
- package/dist/io/xlsx/theme-xml.js +8 -5
- package/dist/io/xlsx/threaded-comments.js +20 -19
- package/dist/io/xlsx/workbook-xml.js +43 -36
- package/dist/io/xlsx/worksheet-xml.d.ts +1 -1
- package/dist/io/xlsx/worksheet-xml.js +47 -52
- package/dist/io/xlsx/write-stream.d.ts +14 -1
- package/dist/io/xlsx/write-stream.js +40 -33
- package/dist/io/xlsx/write.d.ts +5 -0
- package/dist/io/xlsx/write.js +155 -131
- package/dist/sha512.d.ts +2 -0
- package/dist/sha512.js +146 -0
- package/dist/token-set.d.ts +15 -0
- package/dist/token-set.js +4 -0
- package/dist/vba/bytes.d.ts +1 -2
- package/dist/vba/bytes.js +1 -12
- package/dist/vba/cfb-format.d.ts +24 -0
- package/dist/vba/cfb-format.js +12 -0
- package/dist/vba/cfb-writer.js +17 -17
- package/dist/vba/cfb.js +1 -7
- package/dist/vba/dir-records.d.ts +53 -0
- package/dist/vba/dir-records.js +28 -0
- package/dist/vba/ms-ovba.js +32 -11
- package/dist/vba/project-editor.js +5 -34
- package/dist/vba/project.js +15 -31
- package/dist/vba/vba-encoding.js +1 -1
- package/dist/xml/xml-read.d.ts +117 -72
- package/dist/xml/xml-read.js +123 -169
- package/dist/xml/xml-scan.d.ts +100 -0
- package/dist/xml/xml-scan.js +208 -0
- package/dist/xml/xml.d.ts +44 -2
- package/dist/xml/xml.js +17 -5
- package/package.json +22 -6
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 |
|
|
193
|
-
| `@shbernal/ts-xlsx/core` | `Workbook`, `Worksheet`, `Cell`, styles, values, addresses |
|
|
194
|
-
| `@shbernal/ts-xlsx/xlsx` | `readXlsx`, `writeXlsx`/`writeXlsxAsync`, the streaming
|
|
195
|
-
| `@shbernal/ts-xlsx/xlsb` | `readXlsb` |
|
|
196
|
-
| `@shbernal/ts-xlsx/csv` | `readCsv`, `writeCsv`, `writeCsvText` |
|
|
197
|
-
| `@shbernal/ts-xlsx/
|
|
198
|
-
| `@shbernal/ts-xlsx/
|
|
199
|
-
| `@shbernal/ts-xlsx/
|
|
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
|
|
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`
|
package/dist/bytes.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/core/address.d.ts
CHANGED
|
@@ -52,10 +52,21 @@ export interface RangeAddress {
|
|
|
52
52
|
/** Canonical `tl:br` form: `"A1:B2"`, `"1:1"` (rows), `"A:A"` (columns). */
|
|
53
53
|
readonly dimensions: string;
|
|
54
54
|
}
|
|
55
|
-
/** 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`. */
|
|
56
58
|
export declare function numberToColumn(n: number): string;
|
|
57
59
|
/** Convert column letters to a 1-based number (`"A" → 1`, `"AA" → 27`). */
|
|
58
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;
|
|
59
70
|
/**
|
|
60
71
|
* Decode a single cell/row/column reference into `{address, col, row}`. Anchoring
|
|
61
72
|
* `$` signs are accepted and dropped; an absent axis is `undefined`.
|
|
@@ -81,11 +92,22 @@ export interface CellPosition {
|
|
|
81
92
|
export declare function decodeCellRef(reference: string): CellPosition;
|
|
82
93
|
/**
|
|
83
94
|
* {@link decodeCellRef} for a reference that came out of a file rather than out of a caller:
|
|
84
|
-
* `undefined` for anything that does not name one cell
|
|
85
|
-
* column, or outright garbage. A foreign producer writes
|
|
86
|
-
* throwing over when the reading code's answer is simply "then
|
|
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".
|
|
87
99
|
*/
|
|
88
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;
|
|
89
111
|
/**
|
|
90
112
|
* Decode a range reference (`A1:B2`, `$1:$1`, `Sheet1!$A:$A`) into its corners and
|
|
91
113
|
* canonical dimensions. A single reference collapses to a degenerate range whose
|
package/dist/core/address.js
CHANGED
|
@@ -16,9 +16,7 @@ export function rectsOverlap(a, b) {
|
|
|
16
16
|
const SINGLE_REF = /^\$?([A-Z]*)\$?(\d*)$/;
|
|
17
17
|
const SHEET_PREFIX = /^(?:(?:'((?:[^']|'')*)')|([^'!]+))!(.*)$/;
|
|
18
18
|
export function numberToColumn(n) {
|
|
19
|
-
|
|
20
|
-
throw new RangeError(`column ${n} is out of bounds: Excel supports 1..${MAX_COLUMN}`);
|
|
21
|
-
}
|
|
19
|
+
assertColumnInBounds(n);
|
|
22
20
|
let letters = '';
|
|
23
21
|
let remaining = n;
|
|
24
22
|
while (remaining > 0) {
|
|
@@ -45,12 +43,11 @@ export function columnToNumber(letters) {
|
|
|
45
43
|
}
|
|
46
44
|
return n;
|
|
47
45
|
}
|
|
46
|
+
export function encodeCornerRef(col, row) {
|
|
47
|
+
return `${col !== undefined ? numberToColumn(col) : ''}${row !== undefined ? row : ''}`;
|
|
48
|
+
}
|
|
48
49
|
function makeCellAddress(col, row) {
|
|
49
|
-
return {
|
|
50
|
-
address: `${col !== undefined ? numberToColumn(col) : ''}${row !== undefined ? row : ''}`,
|
|
51
|
-
col,
|
|
52
|
-
row,
|
|
53
|
-
};
|
|
50
|
+
return { address: encodeCornerRef(col, row), col, row };
|
|
54
51
|
}
|
|
55
52
|
export function decodeAddress(reference) {
|
|
56
53
|
const match = SINGLE_REF.exec(reference);
|
|
@@ -73,13 +70,28 @@ export function decodeCellRef(reference) {
|
|
|
73
70
|
}
|
|
74
71
|
return { col, row };
|
|
75
72
|
}
|
|
73
|
+
function rowCanExist(row) {
|
|
74
|
+
return row === undefined || (Number.isInteger(row) && row >= 1 && row <= MAX_ROW);
|
|
75
|
+
}
|
|
76
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;
|
|
77
88
|
try {
|
|
78
|
-
|
|
89
|
+
range = decodeRange(reference);
|
|
79
90
|
}
|
|
80
91
|
catch {
|
|
81
92
|
return undefined;
|
|
82
93
|
}
|
|
94
|
+
return rowCanExist(range.top) && rowCanExist(range.bottom) ? range : undefined;
|
|
83
95
|
}
|
|
84
96
|
export function decodeRange(reference) {
|
|
85
97
|
const prefix = SHEET_PREFIX.exec(reference);
|
|
@@ -43,7 +43,7 @@ export interface CustomFilterPredicate {
|
|
|
43
43
|
}
|
|
44
44
|
export type CustomFilterOperator = 'equal' | 'notEqual' | 'lessThan' | 'lessThanOrEqual' | 'greaterThan' | 'greaterThanOrEqual';
|
|
45
45
|
/** Narrow a raw `operator` attribute to a known {@link CustomFilterOperator}. */
|
|
46
|
-
export declare
|
|
46
|
+
export declare const isCustomFilterOperator: (value: string) => value is CustomFilterOperator;
|
|
47
47
|
/**
|
|
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
|
|
@@ -53,3 +53,14 @@ export declare function isCustomFilterOperator(value: string): value is CustomFi
|
|
|
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;
|
package/dist/core/autofilter.js
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { AuthoringError } from '../errors.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
2
|
+
import { tokenSet } from '../token-set.js';
|
|
3
|
+
import { decodeRange, encodeAddress } from './address.js';
|
|
4
|
+
import { isDeletedSpan, shiftIndex } from './grid-shift.js';
|
|
5
|
+
export const isCustomFilterOperator = tokenSet({
|
|
6
|
+
equal: true,
|
|
7
|
+
notEqual: true,
|
|
8
|
+
lessThan: true,
|
|
9
|
+
lessThanOrEqual: true,
|
|
10
|
+
greaterThan: true,
|
|
11
|
+
greaterThanOrEqual: true,
|
|
12
|
+
});
|
|
14
13
|
export function canonicalizeAutoFilter(input) {
|
|
15
14
|
const ref = typeof input === 'string' ? input : input.ref;
|
|
16
15
|
const { top, left, bottom, right, dimensions } = decodeRange(ref);
|
|
@@ -37,3 +36,27 @@ function canonicalizeColumn(column, width) {
|
|
|
37
36
|
}
|
|
38
37
|
return column;
|
|
39
38
|
}
|
|
39
|
+
export function shiftAutoFilter(filter, axis, start, count, delta) {
|
|
40
|
+
const { top, left, bottom, right } = decodeRange(filter.ref);
|
|
41
|
+
if (top === undefined || left === undefined || bottom === undefined || right === undefined) {
|
|
42
|
+
return filter;
|
|
43
|
+
}
|
|
44
|
+
const [lo, hi] = axis === 'row' ? [top, bottom] : [left, right];
|
|
45
|
+
if (isDeletedSpan(lo, hi, start, count))
|
|
46
|
+
return undefined;
|
|
47
|
+
const movedLo = shiftIndex(lo, start, count, delta, axis);
|
|
48
|
+
const movedHi = shiftIndex(hi, start, count, delta, axis);
|
|
49
|
+
if (axis === 'row') {
|
|
50
|
+
const ref = `${encodeAddress(left, movedLo)}:${encodeAddress(right, movedHi)}`;
|
|
51
|
+
return { ref, columns: filter.columns };
|
|
52
|
+
}
|
|
53
|
+
const ref = `${encodeAddress(movedLo, top)}:${encodeAddress(movedHi, bottom)}`;
|
|
54
|
+
const columns = [];
|
|
55
|
+
for (const column of filter.columns) {
|
|
56
|
+
const absolute = left + column.colId;
|
|
57
|
+
if (isDeletedSpan(absolute, absolute, start, count))
|
|
58
|
+
continue;
|
|
59
|
+
columns.push({ ...column, colId: shiftIndex(absolute, start, count, delta, axis) - movedLo });
|
|
60
|
+
}
|
|
61
|
+
return { ref, columns };
|
|
62
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare abstract class AxisHandle<P extends object> {
|
|
2
|
+
/** This position's format record, or `undefined` when it has none. Never fabricates one. */
|
|
3
|
+
protected abstract propertiesOf(): P | undefined;
|
|
4
|
+
/** This position's format record, created if it does not exist yet. */
|
|
5
|
+
protected abstract ensureProperties(): P;
|
|
6
|
+
protected read<K extends keyof P>(key: K): P[K] | undefined;
|
|
7
|
+
protected write<K extends keyof P>(key: K, value: P[K]): void;
|
|
8
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export class AxisHandle {
|
|
2
|
+
read(key) {
|
|
3
|
+
return this.propertiesOf()?.[key];
|
|
4
|
+
}
|
|
5
|
+
write(key, value) {
|
|
6
|
+
if (value === undefined) {
|
|
7
|
+
const properties = this.propertiesOf();
|
|
8
|
+
if (properties !== undefined)
|
|
9
|
+
delete properties[key];
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
this.ensureProperties()[key] = value;
|
|
13
|
+
}
|
|
14
|
+
}
|
package/dist/core/cell.d.ts
CHANGED
|
@@ -138,6 +138,30 @@ export declare class Cell {
|
|
|
138
138
|
get note(): string | undefined;
|
|
139
139
|
set note(note: string | undefined);
|
|
140
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Whether a cell carries formatting of its own: one of the {@link CellStyle} facets, the quote-prefix
|
|
143
|
+
* flag, or a link to a named style. Driven by {@link CELL_STYLE_FACETS}, so a facet added to the
|
|
144
|
+
* tuple reaches every "is this cell blank" decision without anyone remembering to widen a literal.
|
|
145
|
+
*
|
|
146
|
+
* Row- and column-inherited formatting is not the cell's own and does not count. Neither does a
|
|
147
|
+
* note: it lives in the comments part, not the cell's `<c>` element, so a writer deciding whether an
|
|
148
|
+
* empty cell needs serialising asks exactly this. A caller asking whether the cell is *used* wants
|
|
149
|
+
* {@link cellCarriesContent}, which is this plus the value and the note.
|
|
150
|
+
*/
|
|
151
|
+
export declare function cellHasOwnStyle(cell: Cell): boolean;
|
|
152
|
+
/**
|
|
153
|
+
* Whether a cell carries anything at all: a value, formatting of its own, or a note. This is the
|
|
154
|
+
* used-range test, the "carrying anything (data or its own formatting)" that `rowCount` and
|
|
155
|
+
* `columnCount` promise, and it is deliberately wider than "holds a value": someone who pre-formats
|
|
156
|
+
* an empty band has
|
|
157
|
+
* laid claim to those cells, and an append that treats them as free ground writes over the layout
|
|
158
|
+
* they just built; someone who notes an empty cell has put content in it by the note's own
|
|
159
|
+
* definition.
|
|
160
|
+
*
|
|
161
|
+
* A cell merely materialised by `getCell` and then left alone carries nothing, which is what keeps
|
|
162
|
+
* reading a far address from growing the sheet.
|
|
163
|
+
*/
|
|
164
|
+
export declare function cellCarriesContent(cell: Cell): boolean;
|
|
141
165
|
export declare function applyCellStyle(cell: Cell, style: Readonly<CellStyle>): void;
|
|
142
166
|
export declare function copyCellContent(source: CellModel, target: Cell): void;
|
|
143
167
|
export declare function cellToModel(cell: Cell): CellModel;
|
package/dist/core/cell.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { assertColumnInBounds, assertRowInBounds, encodeAddress } from './address.js';
|
|
2
2
|
import { NAMED_STYLE_ID } from './internal.js';
|
|
3
|
-
import { assignStyleFacets, } from './style.js';
|
|
3
|
+
import { assignStyleFacets, CELL_STYLE_FACETS, } from './style.js';
|
|
4
4
|
import { cellValueToText, coerceCellValue, detectValueType, } from './value.js';
|
|
5
5
|
export class Cell {
|
|
6
6
|
row;
|
|
@@ -106,6 +106,14 @@ export class Cell {
|
|
|
106
106
|
this.#note = note;
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
+
export function cellHasOwnStyle(cell) {
|
|
110
|
+
return (CELL_STYLE_FACETS.some((facet) => cell[facet] !== undefined) ||
|
|
111
|
+
cell.quotePrefix === true ||
|
|
112
|
+
cell[NAMED_STYLE_ID] !== undefined);
|
|
113
|
+
}
|
|
114
|
+
export function cellCarriesContent(cell) {
|
|
115
|
+
return cell.value !== null || cell.note !== undefined || cellHasOwnStyle(cell);
|
|
116
|
+
}
|
|
109
117
|
export function applyCellStyle(cell, style) {
|
|
110
118
|
assignStyleFacets(cell, style);
|
|
111
119
|
}
|
|
@@ -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
|
|
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
|
|
42
|
-
return
|
|
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;
|
package/dist/core/column.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { AxisHandle } from './axis-handle.ts';
|
|
1
2
|
import type { Cell } from './cell.ts';
|
|
2
3
|
import { type AssertNever } from './internal.ts';
|
|
3
4
|
import type { Alignment, Border, Fill, Font, Protection } from './style.ts';
|
|
4
5
|
import type { CellValue } from './value.ts';
|
|
5
6
|
import type { ColumnProperties, Worksheet } from './worksheet.ts';
|
|
6
|
-
export declare class Column {
|
|
7
|
+
export declare class Column extends AxisHandle<ColumnProperties> {
|
|
7
8
|
#private;
|
|
8
9
|
/** 1-based column index. Fixed for this handle's lifetime. */
|
|
9
10
|
readonly index: number;
|
|
@@ -18,6 +19,8 @@ export declare class Column {
|
|
|
18
19
|
* is formatted, and they create the record on first write.
|
|
19
20
|
*/
|
|
20
21
|
get properties(): Readonly<ColumnProperties> | undefined;
|
|
22
|
+
protected propertiesOf(): ColumnProperties | undefined;
|
|
23
|
+
protected ensureProperties(): ColumnProperties;
|
|
21
24
|
/**
|
|
22
25
|
* Stable key naming this column so a keyed-object row (see {@link Worksheet.addRow}) can place a
|
|
23
26
|
* value under it by name rather than position. In-memory only: never serialized to OOXML.
|
package/dist/core/column.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { assertColumnInBounds, encodeAddress, numberToColumn } from './address.js';
|
|
2
|
+
import { AxisHandle } from './axis-handle.js';
|
|
2
3
|
import { INTERNAL } from './internal.js';
|
|
3
|
-
export class Column {
|
|
4
|
+
export class Column extends AxisHandle {
|
|
4
5
|
#sheet;
|
|
5
6
|
index;
|
|
6
7
|
constructor(sheet, index) {
|
|
8
|
+
super();
|
|
7
9
|
assertColumnInBounds(index);
|
|
8
10
|
this.#sheet = sheet;
|
|
9
11
|
this.index = index;
|
|
@@ -12,73 +14,79 @@ export class Column {
|
|
|
12
14
|
return numberToColumn(this.index);
|
|
13
15
|
}
|
|
14
16
|
get properties() {
|
|
17
|
+
return this.propertiesOf();
|
|
18
|
+
}
|
|
19
|
+
propertiesOf() {
|
|
15
20
|
return this.#sheet[INTERNAL].columnPropertiesOf(this.index);
|
|
16
21
|
}
|
|
22
|
+
ensureProperties() {
|
|
23
|
+
return this.#sheet[INTERNAL].ensureColumnProperties(this.index);
|
|
24
|
+
}
|
|
17
25
|
get key() {
|
|
18
|
-
return this
|
|
26
|
+
return this.read('key');
|
|
19
27
|
}
|
|
20
28
|
set key(key) {
|
|
21
|
-
this
|
|
29
|
+
this.write('key', key);
|
|
22
30
|
}
|
|
23
31
|
get width() {
|
|
24
|
-
return this
|
|
32
|
+
return this.read('width');
|
|
25
33
|
}
|
|
26
34
|
set width(width) {
|
|
27
|
-
this
|
|
35
|
+
this.write('width', width);
|
|
28
36
|
}
|
|
29
37
|
get hidden() {
|
|
30
|
-
return this
|
|
38
|
+
return this.read('hidden');
|
|
31
39
|
}
|
|
32
40
|
set hidden(hidden) {
|
|
33
|
-
this
|
|
41
|
+
this.write('hidden', hidden);
|
|
34
42
|
}
|
|
35
43
|
get outlineLevel() {
|
|
36
|
-
return this
|
|
44
|
+
return this.read('outlineLevel');
|
|
37
45
|
}
|
|
38
46
|
set outlineLevel(outlineLevel) {
|
|
39
|
-
this
|
|
47
|
+
this.write('outlineLevel', outlineLevel);
|
|
40
48
|
}
|
|
41
49
|
get collapsed() {
|
|
42
|
-
return this
|
|
50
|
+
return this.read('collapsed');
|
|
43
51
|
}
|
|
44
52
|
set collapsed(collapsed) {
|
|
45
|
-
this
|
|
53
|
+
this.write('collapsed', collapsed);
|
|
46
54
|
}
|
|
47
55
|
get fill() {
|
|
48
|
-
return this
|
|
56
|
+
return this.read('fill');
|
|
49
57
|
}
|
|
50
58
|
set fill(fill) {
|
|
51
|
-
this
|
|
59
|
+
this.write('fill', fill);
|
|
52
60
|
}
|
|
53
61
|
get numFmt() {
|
|
54
|
-
return this
|
|
62
|
+
return this.read('numFmt');
|
|
55
63
|
}
|
|
56
64
|
set numFmt(numFmt) {
|
|
57
|
-
this
|
|
65
|
+
this.write('numFmt', numFmt);
|
|
58
66
|
}
|
|
59
67
|
get font() {
|
|
60
|
-
return this
|
|
68
|
+
return this.read('font');
|
|
61
69
|
}
|
|
62
70
|
set font(font) {
|
|
63
|
-
this
|
|
71
|
+
this.write('font', font);
|
|
64
72
|
}
|
|
65
73
|
get border() {
|
|
66
|
-
return this
|
|
74
|
+
return this.read('border');
|
|
67
75
|
}
|
|
68
76
|
set border(border) {
|
|
69
|
-
this
|
|
77
|
+
this.write('border', border);
|
|
70
78
|
}
|
|
71
79
|
get alignment() {
|
|
72
|
-
return this
|
|
80
|
+
return this.read('alignment');
|
|
73
81
|
}
|
|
74
82
|
set alignment(alignment) {
|
|
75
|
-
this
|
|
83
|
+
this.write('alignment', alignment);
|
|
76
84
|
}
|
|
77
85
|
get protection() {
|
|
78
|
-
return this
|
|
86
|
+
return this.read('protection');
|
|
79
87
|
}
|
|
80
88
|
set protection(protection) {
|
|
81
|
-
this
|
|
89
|
+
this.write('protection', protection);
|
|
82
90
|
}
|
|
83
91
|
getCell(row) {
|
|
84
92
|
return this.#sheet.getCell(encodeAddress(this.index, row));
|
|
@@ -98,16 +106,4 @@ export class Column {
|
|
|
98
106
|
this.getCell(index + 1).value = value;
|
|
99
107
|
});
|
|
100
108
|
}
|
|
101
|
-
#read(key) {
|
|
102
|
-
return this.#sheet[INTERNAL].columnPropertiesOf(this.index)?.[key];
|
|
103
|
-
}
|
|
104
|
-
#write(key, value) {
|
|
105
|
-
if (value === undefined) {
|
|
106
|
-
const properties = this.#sheet[INTERNAL].columnPropertiesOf(this.index);
|
|
107
|
-
if (properties !== undefined)
|
|
108
|
-
delete properties[key];
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
this.#sheet[INTERNAL].ensureColumnProperties(this.index)[key] = value;
|
|
112
|
-
}
|
|
113
109
|
}
|
|
@@ -10,6 +10,11 @@ export declare class ConditionalFormattingOverlay {
|
|
|
10
10
|
add(formatting: ConditionalFormatting): void;
|
|
11
11
|
/** The conditional formattings on this sheet, each bound to its target range, in insertion order. */
|
|
12
12
|
get entries(): readonly ConditionalFormatting[];
|
|
13
|
+
/**
|
|
14
|
+
* Re-anchor every rule set through a row or column splice, so a highlight keeps covering the cells
|
|
15
|
+
* it was written for. A rule set whose every target area fell inside a deleted span goes with them.
|
|
16
|
+
*/
|
|
17
|
+
shift(axis: 'row' | 'col', start: number, count: number, delta: number): void;
|
|
13
18
|
/** Drop every conditional formatting, leaving the overlay empty. */
|
|
14
19
|
clear(): void;
|
|
15
20
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { cloneConditionalFormatting } from './conditional-formatting.js';
|
|
2
|
+
import { replaceContents } from './containers.js';
|
|
3
|
+
import { shiftSqref } from './merge.js';
|
|
2
4
|
export class ConditionalFormattingOverlay {
|
|
3
5
|
#entries = [];
|
|
4
6
|
add(formatting) {
|
|
@@ -7,6 +9,15 @@ export class ConditionalFormattingOverlay {
|
|
|
7
9
|
get entries() {
|
|
8
10
|
return this.#entries;
|
|
9
11
|
}
|
|
12
|
+
shift(axis, start, count, delta) {
|
|
13
|
+
const entries = [];
|
|
14
|
+
for (const entry of this.#entries) {
|
|
15
|
+
const ref = shiftSqref(entry.ref, axis, start, count, delta);
|
|
16
|
+
if (ref !== undefined)
|
|
17
|
+
entries.push({ ...entry, ref });
|
|
18
|
+
}
|
|
19
|
+
replaceContents(this.#entries, entries);
|
|
20
|
+
}
|
|
10
21
|
clear() {
|
|
11
22
|
this.#entries.length = 0;
|
|
12
23
|
}
|