@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.
- package/README.md +20 -10
- package/dist/bytes.d.ts +24 -0
- package/dist/bytes.js +34 -0
- package/dist/core/address.d.ts +72 -3
- package/dist/core/address.js +50 -10
- package/dist/core/autofilter.d.ts +13 -2
- package/dist/core/autofilter.js +35 -10
- package/dist/core/cell.d.ts +17 -16
- package/dist/core/cell.js +3 -7
- package/dist/core/color-resolution.d.ts +8 -8
- package/dist/core/color-resolution.js +7 -8
- package/dist/core/column.d.ts +8 -9
- package/dist/core/column.js +2 -4
- package/dist/core/comment-thread.d.ts +32 -23
- package/dist/core/comment-thread.js +4 -4
- package/dist/core/conditional-formatting-overlay.d.ts +6 -1
- package/dist/core/conditional-formatting-overlay.js +11 -0
- package/dist/core/conditional-formatting.d.ts +48 -8
- package/dist/core/conditional-formatting.js +88 -0
- package/dist/core/data-validation-overlay.d.ts +12 -1
- package/dist/core/data-validation-overlay.js +21 -2
- package/dist/core/data-validation.d.ts +12 -5
- package/dist/core/data-validation.js +34 -0
- package/dist/core/date.d.ts +3 -3
- package/dist/core/formula.d.ts +6 -6
- package/dist/core/grid-edits.d.ts +16 -0
- package/dist/core/grid-edits.js +29 -11
- package/dist/core/grid-shift.d.ts +19 -0
- package/dist/core/grid-shift.js +8 -0
- package/dist/core/image.d.ts +39 -2
- package/dist/core/image.js +10 -0
- package/dist/core/internal.d.ts +9 -1
- package/dist/core/limits.d.ts +4 -4
- package/dist/core/merge.d.ts +17 -12
- package/dist/core/merge.js +36 -5
- package/dist/core/page-setup.d.ts +20 -6
- package/dist/core/page-setup.js +8 -1
- package/dist/core/pivot-table.d.ts +4 -4
- package/dist/core/pivot-table.js +29 -20
- package/dist/core/preserved.d.ts +3 -3
- package/dist/core/protection.d.ts +7 -4
- package/dist/core/protection.js +20 -14
- package/dist/core/range.d.ts +16 -14
- package/dist/core/range.js +5 -5
- package/dist/core/row-input.d.ts +9 -8
- package/dist/core/row-input.js +12 -10
- package/dist/core/row.d.ts +7 -8
- package/dist/core/row.js +2 -4
- package/dist/core/style.d.ts +41 -14
- package/dist/core/style.js +82 -67
- package/dist/core/table-style.d.ts +9 -9
- package/dist/core/table-style.js +1 -1
- package/dist/core/table.d.ts +53 -51
- package/dist/core/table.js +83 -41
- package/dist/core/theme.d.ts +16 -44
- package/dist/core/theme.js +6 -148
- package/dist/core/value.d.ts +7 -7
- package/dist/core/workbook-protection.d.ts +2 -2
- package/dist/core/workbook-styles.d.ts +26 -0
- package/dist/core/workbook-styles.js +48 -0
- package/dist/core/workbook-theme.d.ts +25 -0
- package/dist/core/workbook-theme.js +49 -0
- package/dist/core/workbook-vba.d.ts +17 -0
- package/dist/core/workbook-vba.js +79 -0
- package/dist/core/workbook.d.ts +133 -87
- package/dist/core/workbook.js +74 -133
- package/dist/core/worksheet-comments.d.ts +17 -0
- package/dist/core/worksheet-comments.js +70 -0
- package/dist/core/worksheet-model.d.ts +4 -4
- package/dist/core/worksheet-model.js +1 -0
- package/dist/core/worksheet-pictures.d.ts +29 -0
- package/dist/core/worksheet-pictures.js +42 -0
- package/dist/core/worksheet.d.ts +100 -79
- package/dist/core/worksheet.js +63 -142
- package/dist/customui/errors.d.ts +1 -1
- package/dist/customui/ribbon.d.ts +3 -3
- package/dist/customui/ribbon.js +24 -23
- package/dist/entries/core.d.ts +7 -7
- package/dist/entries/core.js +1 -1
- 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/vba.d.ts +1 -1
- package/dist/entries/vba.js +1 -1
- package/dist/entries/xlsx.d.ts +1 -1
- package/dist/entries/xlsx.js +1 -1
- package/dist/errors.d.ts +12 -12
- package/dist/io/csv/read.js +2 -3
- package/dist/io/csv/write.d.ts +19 -4
- package/dist/io/csv/write.js +34 -4
- package/dist/io/opc/errors.d.ts +6 -6
- package/dist/io/opc/inflate.d.ts +1 -1
- package/dist/io/opc/inflate.js +2 -12
- package/dist/io/opc/namespaces.d.ts +1 -1
- package/dist/io/opc/read-opc.d.ts +19 -3
- package/dist/io/opc/read-opc.js +14 -8
- package/dist/io/opc/read-options.d.ts +2 -2
- package/dist/io/opc/rels.d.ts +11 -0
- package/dist/io/opc/rels.js +3 -2
- package/dist/io/opc/sniff-format.d.ts +2 -2
- package/dist/io/style/xf-style.d.ts +44 -7
- package/dist/io/style/xf-style.js +27 -0
- package/dist/io/xlsb/errors.d.ts +1 -1
- package/dist/io/xlsb/formula.d.ts +4 -4
- package/dist/io/xlsb/primitives.d.ts +8 -8
- package/dist/io/xlsb/ptg-functions.d.ts +1 -1
- package/dist/io/xlsb/read-styles.js +2 -20
- package/dist/io/xlsb/read.d.ts +2 -2
- package/dist/io/xlsx/cell-accumulator.d.ts +26 -9
- package/dist/io/xlsx/cell-accumulator.js +89 -46
- package/dist/io/xlsx/cell-value.d.ts +3 -3
- package/dist/io/xlsx/cell-value.js +9 -6
- package/dist/io/xlsx/color-xml.d.ts +1 -1
- package/dist/io/xlsx/color-xml.js +18 -23
- package/dist/io/xlsx/comments.d.ts +5 -5
- package/dist/io/xlsx/comments.js +17 -8
- package/dist/io/xlsx/conditional-formatting.d.ts +4 -4
- package/dist/io/xlsx/conditional-formatting.js +64 -58
- package/dist/io/xlsx/data-validation.d.ts +3 -3
- package/dist/io/xlsx/data-validation.js +35 -33
- package/dist/io/xlsx/errors.d.ts +3 -3
- package/dist/io/xlsx/hyperlinks.d.ts +4 -2
- package/dist/io/xlsx/hyperlinks.js +10 -18
- package/dist/io/xlsx/images.d.ts +1 -1
- package/dist/io/xlsx/images.js +25 -28
- package/dist/io/xlsx/namespaces.d.ts +2 -2
- package/dist/io/xlsx/package-plan.js +5 -2
- package/dist/io/xlsx/read-pivot.js +4 -13
- package/dist/io/xlsx/read-rows.d.ts +13 -13
- package/dist/io/xlsx/read-rows.js +19 -55
- package/dist/io/xlsx/read-shared-strings.js +7 -44
- package/dist/io/xlsx/read-styles.d.ts +3 -3
- package/dist/io/xlsx/read-styles.js +42 -66
- package/dist/io/xlsx/read-worksheet.js +73 -129
- package/dist/io/xlsx/read.d.ts +4 -6
- package/dist/io/xlsx/read.js +104 -142
- package/dist/io/xlsx/rich-runs.d.ts +41 -7
- package/dist/io/xlsx/rich-runs.js +94 -30
- package/dist/io/xlsx/rich-text.d.ts +1 -1
- package/dist/io/xlsx/shared-strings.d.ts +2 -2
- package/dist/io/xlsx/sheet-properties.d.ts +1 -1
- package/dist/io/xlsx/sheet-properties.js +14 -10
- package/dist/io/xlsx/styles.d.ts +9 -22
- package/dist/io/xlsx/styles.js +65 -89
- package/dist/io/xlsx/tables.d.ts +2 -2
- package/dist/io/xlsx/tables.js +20 -25
- package/dist/io/xlsx/theme-xml.d.ts +37 -0
- package/dist/io/xlsx/theme-xml.js +145 -0
- package/dist/io/xlsx/threaded-comments.d.ts +18 -27
- package/dist/io/xlsx/threaded-comments.js +26 -38
- package/dist/io/xlsx/workbook-xml.js +14 -8
- package/dist/io/xlsx/worksheet-xml.d.ts +2 -2
- package/dist/io/xlsx/worksheet-xml.js +40 -48
- package/dist/io/xlsx/write-stream.d.ts +23 -16
- package/dist/io/xlsx/write-stream.js +13 -14
- package/dist/io/xlsx/write.d.ts +8 -7
- package/dist/io/xlsx/write.js +6 -3
- package/dist/io/xlsx/x14-ext.d.ts +1 -1
- package/dist/sha512.d.ts +2 -0
- package/dist/sha512.js +146 -0
- package/dist/vba/bytes.d.ts +11 -0
- package/dist/vba/bytes.js +29 -0
- package/dist/vba/cfb-writer.d.ts +1 -1
- package/dist/vba/cfb.d.ts +1 -1
- package/dist/vba/cfb.js +15 -51
- package/dist/vba/codepage.d.ts +2 -2
- package/dist/vba/dir-records.d.ts +53 -0
- package/dist/vba/dir-records.js +28 -0
- package/dist/vba/errors.d.ts +2 -2
- package/dist/vba/index.d.ts +2 -2
- package/dist/vba/index.js +2 -2
- package/dist/vba/ms-ovba.d.ts +1 -1
- package/dist/vba/ms-ovba.js +1 -3
- package/dist/vba/project-editor.d.ts +9 -9
- package/dist/vba/project-editor.js +6 -44
- package/dist/vba/project.d.ts +5 -5
- package/dist/vba/project.js +3 -28
- package/dist/vba/vba-encoding.d.ts +1 -1
- package/dist/xml/errors.d.ts +1 -1
- package/dist/xml/xml-read.d.ts +98 -9
- package/dist/xml/xml-read.js +70 -0
- package/dist/xml/xml.d.ts +74 -10
- package/dist/xml/xml.js +39 -7
- 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 |
|
|
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
|
@@ -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
|
|
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
|
|
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
|
package/dist/core/address.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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;
|
package/dist/core/autofilter.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { AuthoringError } from '../errors.js';
|
|
2
|
-
import { decodeRange } from './address.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
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
|
+
}
|
package/dist/core/cell.d.ts
CHANGED
|
@@ -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
|
|
7
|
-
* font, border, alignment, protection, quote-prefix, and note
|
|
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
|
|
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
|
|
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})
|
|
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
|
|
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
|
|
53
|
-
* to the workbook default, which is exactly what such a cell renders in
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
20
|
-
|
|
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
|
|
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
|
|
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
|
|
11
|
-
* fully transparent colours would be absurd
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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;
|