@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/dist/core/style.js
CHANGED
|
@@ -1,84 +1,89 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
'solid',
|
|
14
|
-
'gray125',
|
|
15
|
-
'darkGray',
|
|
16
|
-
'mediumGray',
|
|
17
|
-
'lightGray',
|
|
18
|
-
'gray0625',
|
|
19
|
-
'darkHorizontal',
|
|
20
|
-
'darkVertical',
|
|
21
|
-
'darkDown',
|
|
22
|
-
'darkUp',
|
|
23
|
-
'darkGrid',
|
|
24
|
-
'darkTrellis',
|
|
25
|
-
'lightHorizontal',
|
|
26
|
-
'lightVertical',
|
|
27
|
-
'lightDown',
|
|
28
|
-
'lightUp',
|
|
29
|
-
'lightGrid',
|
|
30
|
-
'lightTrellis',
|
|
31
|
-
]);
|
|
32
|
-
export function isFillPatternType(value) {
|
|
33
|
-
return FILL_PATTERN_TYPES.has(value);
|
|
34
|
-
}
|
|
35
|
-
const BORDER_STYLES = new Set([
|
|
36
|
-
'thin',
|
|
37
|
-
'medium',
|
|
38
|
-
'thick',
|
|
39
|
-
'dashed',
|
|
40
|
-
'dotted',
|
|
41
|
-
'double',
|
|
42
|
-
'hair',
|
|
43
|
-
'mediumDashed',
|
|
44
|
-
'dashDot',
|
|
45
|
-
'mediumDashDot',
|
|
46
|
-
'dashDotDot',
|
|
47
|
-
'mediumDashDotDot',
|
|
48
|
-
'slantDashDot',
|
|
49
|
-
]);
|
|
50
|
-
export function isBorderStyle(value) {
|
|
51
|
-
return BORDER_STYLES.has(value);
|
|
52
|
-
}
|
|
53
|
-
export function isFontVerticalAlignment(value) {
|
|
54
|
-
return value === 'superscript' || value === 'subscript';
|
|
55
|
-
}
|
|
56
|
-
export function isFontScheme(value) {
|
|
57
|
-
return value === 'minor' || value === 'major' || value === 'none';
|
|
58
|
-
}
|
|
59
|
-
const HORIZONTAL_ALIGNMENTS = new Set([
|
|
60
|
-
'general',
|
|
61
|
-
'left',
|
|
62
|
-
'center',
|
|
63
|
-
'right',
|
|
64
|
-
'fill',
|
|
65
|
-
'justify',
|
|
66
|
-
'centerContinuous',
|
|
67
|
-
'distributed',
|
|
68
|
-
]);
|
|
69
|
-
export function isHorizontalAlignment(value) {
|
|
70
|
-
return HORIZONTAL_ALIGNMENTS.has(value);
|
|
71
|
-
}
|
|
72
|
-
const VERTICAL_ALIGNMENTS = new Set([
|
|
73
|
-
'top',
|
|
74
|
-
'center',
|
|
75
|
-
'bottom',
|
|
76
|
-
'justify',
|
|
77
|
-
'distributed',
|
|
78
|
-
]);
|
|
79
|
-
export function isVerticalAlignment(value) {
|
|
80
|
-
return VERTICAL_ALIGNMENTS.has(value);
|
|
1
|
+
import { tokenSet } from '../token-set.js';
|
|
2
|
+
export const isNamedUnderlineStyle = tokenSet({
|
|
3
|
+
none: true,
|
|
4
|
+
single: true,
|
|
5
|
+
double: true,
|
|
6
|
+
singleAccounting: true,
|
|
7
|
+
doubleAccounting: true,
|
|
8
|
+
});
|
|
9
|
+
export function parseArgb(value) {
|
|
10
|
+
const hex = value.startsWith('#') ? value.slice(1) : value;
|
|
11
|
+
const argb = hex.length === 6 ? `FF${hex}` : hex;
|
|
12
|
+
return /^[0-9a-fA-F]{8}$/.test(argb) ? argb : undefined;
|
|
81
13
|
}
|
|
14
|
+
export const isFillPatternType = tokenSet({
|
|
15
|
+
none: true,
|
|
16
|
+
solid: true,
|
|
17
|
+
gray125: true,
|
|
18
|
+
darkGray: true,
|
|
19
|
+
mediumGray: true,
|
|
20
|
+
lightGray: true,
|
|
21
|
+
gray0625: true,
|
|
22
|
+
darkHorizontal: true,
|
|
23
|
+
darkVertical: true,
|
|
24
|
+
darkDown: true,
|
|
25
|
+
darkUp: true,
|
|
26
|
+
darkGrid: true,
|
|
27
|
+
darkTrellis: true,
|
|
28
|
+
lightHorizontal: true,
|
|
29
|
+
lightVertical: true,
|
|
30
|
+
lightDown: true,
|
|
31
|
+
lightUp: true,
|
|
32
|
+
lightGrid: true,
|
|
33
|
+
lightTrellis: true,
|
|
34
|
+
});
|
|
35
|
+
export const isBorderStyle = tokenSet({
|
|
36
|
+
thin: true,
|
|
37
|
+
medium: true,
|
|
38
|
+
thick: true,
|
|
39
|
+
dashed: true,
|
|
40
|
+
dotted: true,
|
|
41
|
+
double: true,
|
|
42
|
+
hair: true,
|
|
43
|
+
mediumDashed: true,
|
|
44
|
+
dashDot: true,
|
|
45
|
+
mediumDashDot: true,
|
|
46
|
+
dashDotDot: true,
|
|
47
|
+
mediumDashDotDot: true,
|
|
48
|
+
slantDashDot: true,
|
|
49
|
+
});
|
|
50
|
+
export const isFontVerticalAlignment = tokenSet({
|
|
51
|
+
superscript: true,
|
|
52
|
+
subscript: true,
|
|
53
|
+
});
|
|
54
|
+
export const isFontScheme = tokenSet({ minor: true, major: true, none: true });
|
|
55
|
+
export const isHorizontalAlignment = tokenSet({
|
|
56
|
+
general: true,
|
|
57
|
+
left: true,
|
|
58
|
+
center: true,
|
|
59
|
+
right: true,
|
|
60
|
+
fill: true,
|
|
61
|
+
justify: true,
|
|
62
|
+
centerContinuous: true,
|
|
63
|
+
distributed: true,
|
|
64
|
+
});
|
|
65
|
+
export const isVerticalAlignment = tokenSet({
|
|
66
|
+
top: true,
|
|
67
|
+
center: true,
|
|
68
|
+
bottom: true,
|
|
69
|
+
justify: true,
|
|
70
|
+
distributed: true,
|
|
71
|
+
});
|
|
72
|
+
export const ALIGNMENT_FACETS = [
|
|
73
|
+
{
|
|
74
|
+
key: 'horizontal',
|
|
75
|
+
kind: 'token',
|
|
76
|
+
isValid: isHorizontalAlignment,
|
|
77
|
+
label: 'horizontal alignment',
|
|
78
|
+
omit: 'general',
|
|
79
|
+
},
|
|
80
|
+
{ key: 'vertical', kind: 'token', isValid: isVerticalAlignment, label: 'vertical alignment' },
|
|
81
|
+
{ key: 'textRotation', kind: 'number' },
|
|
82
|
+
{ key: 'wrapText', kind: 'flag' },
|
|
83
|
+
{ key: 'indent', kind: 'number' },
|
|
84
|
+
{ key: 'shrinkToFit', kind: 'flag' },
|
|
85
|
+
{ key: 'readingOrder', kind: 'number' },
|
|
86
|
+
];
|
|
82
87
|
const CELL_STYLE_FACET_KEYS = {
|
|
83
88
|
fill: true,
|
|
84
89
|
numFmt: true,
|
|
@@ -92,6 +97,11 @@ export function assignStyleFacets(target, source) {
|
|
|
92
97
|
for (const facet of CELL_STYLE_FACETS)
|
|
93
98
|
copyFacet(target, source, facet);
|
|
94
99
|
}
|
|
100
|
+
export function pickStyleFacets(source) {
|
|
101
|
+
const facets = {};
|
|
102
|
+
assignStyleFacets(facets, source);
|
|
103
|
+
return facets;
|
|
104
|
+
}
|
|
95
105
|
function copyFacet(target, source, key) {
|
|
96
106
|
const value = source[key];
|
|
97
107
|
if (value !== undefined)
|
package/dist/core/table.d.ts
CHANGED
|
@@ -5,16 +5,30 @@ import type { CellValue } from './value.ts';
|
|
|
5
5
|
* bakes into the cells rather than storing as table metadata. Every facet ({@link CellStyle}) is
|
|
6
6
|
* optional; only the ones set are applied, leaving the rest of each cell's style untouched. */
|
|
7
7
|
export type TableColumnStyle = Readonly<CellStyle>;
|
|
8
|
-
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
8
|
+
/**
|
|
9
|
+
* The channel a registered table holds into its owning worksheet's grid. A worksheet supplies it
|
|
10
|
+
* when it registers the table; a table built standalone (a unit test, a bare model) has none, so it
|
|
11
|
+
* can be inspected but cannot materialise or append cells, and appending throws rather than
|
|
12
|
+
* silently dropping the values.
|
|
13
|
+
*
|
|
14
|
+
* All three coordinates are 1-based.
|
|
15
|
+
*/
|
|
16
|
+
export interface TableGrid {
|
|
17
|
+
/**
|
|
18
|
+
* Whether the cell at this position already holds a value. The materialiser's round-trip guard
|
|
19
|
+
* asks this and nothing else: it must not create the cell, because asking whether a table's frame
|
|
20
|
+
* is already filled would otherwise fill the grid with the empty cells it was asking about.
|
|
21
|
+
*/
|
|
22
|
+
holdsValue(row: number, col: number): boolean;
|
|
23
|
+
/** Write a value, applying the column's style (if any) to the cell. */
|
|
24
|
+
writeCell(row: number, col: number, value: CellValue, style?: TableColumnStyle): void;
|
|
25
|
+
/**
|
|
26
|
+
* Insert one empty row at `row`, shifting that row and everything below it down by one: how a
|
|
27
|
+
* table with a totals row opens a slot for an appended data row above the totals. Relocating the
|
|
28
|
+
* totals row lives in the grid, which is why this is the grid's job and not the table's.
|
|
29
|
+
*/
|
|
30
|
+
insertRow(row: number): void;
|
|
31
|
+
}
|
|
18
32
|
/**
|
|
19
33
|
* A table's visual style (`<tableStyleInfo>`): the named style to apply plus the banding/highlight
|
|
20
34
|
* toggles. Every field is a tri-state so a round-trip stays faithful: a value present in the source
|
|
@@ -61,7 +75,7 @@ export declare const TOTALS_ROW_SUBTOTAL_CODE: Readonly<Partial<Record<TotalsRow
|
|
|
61
75
|
*/
|
|
62
76
|
export type TotalsRowFunction = 'average' | 'countNums' | 'count' | 'max' | 'min' | 'stdDev' | 'sum' | 'var' | 'custom' | 'none';
|
|
63
77
|
/** Narrow a raw `totalsRowFunction` attribute to a known {@link TotalsRowFunction}. */
|
|
64
|
-
export declare
|
|
78
|
+
export declare const isTotalsRowFunction: (value: string) => value is TotalsRowFunction;
|
|
65
79
|
/** One column of a table: a header name and its optional totals-row behaviour. */
|
|
66
80
|
export interface TableColumn {
|
|
67
81
|
/** The column's header/display name. Must be unique within the table (case-insensitively):
|
|
@@ -127,7 +141,7 @@ export declare class Table {
|
|
|
127
141
|
readonly totalsRowShown: boolean | undefined;
|
|
128
142
|
readonly autoFilter: boolean;
|
|
129
143
|
readonly style: TableStyleInfo | undefined;
|
|
130
|
-
constructor(options: TableOptions,
|
|
144
|
+
constructor(options: TableOptions, grid?: TableGrid);
|
|
131
145
|
get columnCount(): number;
|
|
132
146
|
/** The number of data rows (excludes the header and totals rows). Always defined: a table loaded
|
|
133
147
|
* from a file derives it from the stored range, so reading the height never throws. */
|
package/dist/core/table.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { AuthoringError } from '../errors.js';
|
|
2
|
+
import { tokenSet } from '../token-set.js';
|
|
2
3
|
import { decodeCellRef, encodeAddress } from './address.js';
|
|
4
|
+
import { isDeletedSpan, shiftIndex } from './grid-shift.js';
|
|
3
5
|
function cloneStyleInfo(style) {
|
|
4
6
|
const clone = {};
|
|
5
7
|
if (style.name !== undefined && style.name !== 'None')
|
|
@@ -24,21 +26,18 @@ export const TOTALS_ROW_SUBTOTAL_CODE = {
|
|
|
24
26
|
sum: 109,
|
|
25
27
|
var: 110,
|
|
26
28
|
};
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
export function isTotalsRowFunction(value) {
|
|
40
|
-
return TOTALS_ROW_FUNCTIONS.has(value);
|
|
41
|
-
}
|
|
29
|
+
export const isTotalsRowFunction = tokenSet({
|
|
30
|
+
average: true,
|
|
31
|
+
countNums: true,
|
|
32
|
+
count: true,
|
|
33
|
+
max: true,
|
|
34
|
+
min: true,
|
|
35
|
+
stdDev: true,
|
|
36
|
+
sum: true,
|
|
37
|
+
var: true,
|
|
38
|
+
custom: true,
|
|
39
|
+
none: true,
|
|
40
|
+
});
|
|
42
41
|
const IDENTIFIER = /^[\p{L}\\_][\p{L}\p{N}._]*$/u;
|
|
43
42
|
function disambiguateColumnNames(columns) {
|
|
44
43
|
const seen = new Set();
|
|
@@ -71,9 +70,8 @@ export class Table {
|
|
|
71
70
|
#anchorCol;
|
|
72
71
|
#anchorRow;
|
|
73
72
|
#dataRowCount;
|
|
74
|
-
#
|
|
75
|
-
|
|
76
|
-
constructor(options, writeCell, insertRow) {
|
|
73
|
+
#grid;
|
|
74
|
+
constructor(options, grid) {
|
|
77
75
|
validateTableName(options.name);
|
|
78
76
|
if (options.columns.length === 0) {
|
|
79
77
|
throw new AuthoringError(`table "${options.name}" must declare at least one column`);
|
|
@@ -102,11 +100,12 @@ export class Table {
|
|
|
102
100
|
this.#anchorCol = col;
|
|
103
101
|
this.#anchorRow = row;
|
|
104
102
|
this.#dataRowCount = options.rowCount;
|
|
105
|
-
this.#
|
|
106
|
-
this.#insertRow = insertRow;
|
|
103
|
+
this.#grid = grid;
|
|
107
104
|
if (this.#rowSpan < 1) {
|
|
108
105
|
throw new AuthoringError(`table "${this.name}" has no rows: it needs a header row or at least one data row`);
|
|
109
106
|
}
|
|
107
|
+
if (grid !== undefined)
|
|
108
|
+
this.#materializeFrame(grid);
|
|
110
109
|
}
|
|
111
110
|
get columnCount() {
|
|
112
111
|
return this.columns.length;
|
|
@@ -119,32 +118,67 @@ export class Table {
|
|
|
119
118
|
throw new RangeError(`row has ${values.length} values but table "${this.name}" has ${this.columnCount} columns`);
|
|
120
119
|
}
|
|
121
120
|
const target = this.#anchorRow + (this.headerRow ? 1 : 0) + this.#dataRowCount;
|
|
121
|
+
const grid = this.#grid;
|
|
122
122
|
if (this.totalsRow) {
|
|
123
|
-
if (
|
|
123
|
+
if (grid === undefined) {
|
|
124
124
|
throw new AuthoringError(`table "${this.name}" is not attached to a worksheet: cannot relocate its totals row to append a data row`);
|
|
125
125
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
this.#writeCell?.(target, this.#anchorCol + index, value, this.columns[index]?.style);
|
|
129
|
-
});
|
|
126
|
+
grid.insertRow(target);
|
|
127
|
+
this.#writeRow(grid, target, values);
|
|
130
128
|
return;
|
|
131
129
|
}
|
|
132
130
|
if (values.length > 0) {
|
|
133
|
-
if (
|
|
131
|
+
if (grid === undefined) {
|
|
134
132
|
throw new AuthoringError(`table "${this.name}" is not attached to a worksheet: cannot write appended row values`);
|
|
135
133
|
}
|
|
136
|
-
|
|
137
|
-
this.#writeCell?.(target, this.#anchorCol + index, value, this.columns[index]?.style);
|
|
138
|
-
});
|
|
134
|
+
this.#writeRow(grid, target, values);
|
|
139
135
|
}
|
|
140
136
|
this.#dataRowCount += 1;
|
|
141
137
|
}
|
|
138
|
+
#writeRow(grid, row, values) {
|
|
139
|
+
values.forEach((value, index) => {
|
|
140
|
+
grid.writeCell(row, this.#anchorCol + index, value, this.columns[index]?.style);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
#materializeFrame(grid) {
|
|
144
|
+
if (this.headerRow) {
|
|
145
|
+
const { top, left } = this.region;
|
|
146
|
+
this.columns.forEach((column, index) => {
|
|
147
|
+
const col = left + index;
|
|
148
|
+
if (grid.holdsValue(top, col))
|
|
149
|
+
return;
|
|
150
|
+
grid.writeCell(top, col, column.name);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
if (this.totalsRow) {
|
|
154
|
+
const { left, bottom } = this.region;
|
|
155
|
+
this.columns.forEach((column, index) => {
|
|
156
|
+
const col = left + index;
|
|
157
|
+
if (grid.holdsValue(bottom, col))
|
|
158
|
+
return;
|
|
159
|
+
if (column.totalsRowLabel !== undefined) {
|
|
160
|
+
grid.writeCell(bottom, col, column.totalsRowLabel);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (column.totalsRowFunction === undefined)
|
|
164
|
+
return;
|
|
165
|
+
const code = TOTALS_ROW_SUBTOTAL_CODE[column.totalsRowFunction];
|
|
166
|
+
if (code !== undefined) {
|
|
167
|
+
grid.writeCell(bottom, col, {
|
|
168
|
+
formula: `SUBTOTAL(${code},${this.name}[${column.name}])`,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
else if (column.totalsRowFunction === 'custom' && column.totalsRowFormula !== undefined) {
|
|
172
|
+
grid.writeCell(bottom, col, { formula: column.totalsRowFormula });
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
142
177
|
shiftRows(start, count, delta) {
|
|
143
|
-
if (this.#anchorRow
|
|
178
|
+
if (isDeletedSpan(this.#anchorRow, this.#bottom, start, count))
|
|
144
179
|
return false;
|
|
145
|
-
const
|
|
146
|
-
const
|
|
147
|
-
const bottom = shift(this.#bottom);
|
|
180
|
+
const top = shiftIndex(this.#anchorRow, start, count, delta, 'row');
|
|
181
|
+
const bottom = shiftIndex(this.#bottom, start, count, delta, 'row');
|
|
148
182
|
const span = bottom - top + 1;
|
|
149
183
|
const fixedRows = (this.headerRow ? 1 : 0) + (this.totalsRow ? 1 : 0);
|
|
150
184
|
const dataRows = span - fixedRows;
|
package/dist/core/theme.d.ts
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
export declare const THEME_COLOR_SLOTS: readonly ['lt1', 'dk1', 'lt2', 'dk2', 'accent1', 'accent2', 'accent3', 'accent4', 'accent5', 'accent6', 'hlink', 'folHlink'];
|
|
16
16
|
/** One slot of a theme's colour scheme. */
|
|
17
17
|
export type ThemeColorSlot = (typeof THEME_COLOR_SLOTS)[number];
|
|
18
|
+
/** Narrow a raw `<a:clrScheme>` child name to a known {@link ThemeColorSlot}. */
|
|
19
|
+
export declare function isThemeColorSlot(value: string): value is ThemeColorSlot;
|
|
18
20
|
/**
|
|
19
21
|
* A theme's colour scheme: each slot's colour as a 6-hex `RRGGBB` string. Partial because a foreign
|
|
20
22
|
* theme is free to omit a slot (or express one in a colour model this reader does not decode), and an
|
|
@@ -22,7 +24,7 @@ export type ThemeColorSlot = (typeof THEME_COLOR_SLOTS)[number];
|
|
|
22
24
|
*/
|
|
23
25
|
export type ThemeColorScheme = Readonly<Partial<Record<ThemeColorSlot, string>>>;
|
|
24
26
|
/** The Office default colour scheme, matching the theme part the writer emits for a workbook with none. */
|
|
25
|
-
export declare const DEFAULT_THEME_COLOR_SCHEME:
|
|
27
|
+
export declare const DEFAULT_THEME_COLOR_SCHEME: Readonly<Record<ThemeColorSlot, string>>;
|
|
26
28
|
/**
|
|
27
29
|
* The two typefaces a theme nominates: the `major` face headings use and the `minor` face body text
|
|
28
30
|
* uses. A cell's font reaches them by `scheme="major"`/`scheme="minor"` instead of naming a typeface,
|
package/dist/core/theme.js
CHANGED
|
@@ -12,6 +12,10 @@ export const THEME_COLOR_SLOTS = [
|
|
|
12
12
|
'hlink',
|
|
13
13
|
'folHlink',
|
|
14
14
|
];
|
|
15
|
+
const THEME_COLOR_SLOT_NAMES = new Set(THEME_COLOR_SLOTS);
|
|
16
|
+
export function isThemeColorSlot(value) {
|
|
17
|
+
return THEME_COLOR_SLOT_NAMES.has(value);
|
|
18
|
+
}
|
|
15
19
|
export const DEFAULT_THEME_COLOR_SCHEME = {
|
|
16
20
|
lt1: 'FFFFFF',
|
|
17
21
|
dk1: '000000',
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type Cell } from './cell.ts';
|
|
2
|
+
import type { MergeRect } from './merge.ts';
|
|
3
|
+
/**
|
|
4
|
+
* The sheet storage an extent reads. Held by reference and never reassigned, so the extent always
|
|
5
|
+
* sees the live grid; the value types are irrelevant to it, only which keys are present.
|
|
6
|
+
*/
|
|
7
|
+
export interface ExtentStorage {
|
|
8
|
+
readonly rows: ReadonlyMap<number, ReadonlyMap<number, Cell>>;
|
|
9
|
+
readonly rowProperties: ReadonlyMap<number, unknown>;
|
|
10
|
+
readonly columns: ReadonlyMap<number, unknown>;
|
|
11
|
+
readonly mergeRects: readonly MergeRect[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The last used row and column of a sheet, tracked incrementally. The sheet reports every edit that
|
|
15
|
+
* can move the grid's outer keys; this turns those reports into the two accessors behind
|
|
16
|
+
* `Worksheet.rowCount` and `Worksheet.columnCount`.
|
|
17
|
+
*/
|
|
18
|
+
export declare class UsedExtent {
|
|
19
|
+
#private;
|
|
20
|
+
constructor(storage: ExtentStorage);
|
|
21
|
+
/** Note a cell materialised at `(row, col)`. Whether it goes on to carry anything is not this
|
|
22
|
+
* class's business: the bound is on where a used cell *could* be, and the read confirms it. */
|
|
23
|
+
noteCell(row: number, col: number): void;
|
|
24
|
+
/** Note a row that bounds the used range by declaration: one given properties of its own. */
|
|
25
|
+
noteDeclaredRow(row: number): void;
|
|
26
|
+
/** Note a column that bounds the used range by declaration: one given properties of its own. */
|
|
27
|
+
noteDeclaredColumn(col: number): void;
|
|
28
|
+
/** Note a merged region, which occupies its whole rectangle and so bounds both axes. */
|
|
29
|
+
noteMerge(rect: MergeRect): void;
|
|
30
|
+
/**
|
|
31
|
+
* Report an edit that can pull the grid's outer keys inward: an eviction, a splice, an unmerge, a
|
|
32
|
+
* row replaced wholesale. The bounds are recomputed once, on the next read, rather than adjusted
|
|
33
|
+
* here, because finding the *new* maximum after a deletion is the same scan as finding it from
|
|
34
|
+
* scratch. Appending never invalidates, which is the whole point.
|
|
35
|
+
*/
|
|
36
|
+
invalidate(): void;
|
|
37
|
+
/** Report that the grid has been emptied, which puts the bounds at a known zero rather than
|
|
38
|
+
* stale: nothing is left to scan. */
|
|
39
|
+
reset(): void;
|
|
40
|
+
/** The 1-based index of the last row carrying anything, or 0 when the sheet holds nothing. */
|
|
41
|
+
get lastRow(): number;
|
|
42
|
+
/** The 1-based index of the last column carrying anything, or 0 when the sheet holds nothing. */
|
|
43
|
+
get lastColumn(): number;
|
|
44
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { cellCarriesContent } from './cell.js';
|
|
2
|
+
function rowIsUsed(cols) {
|
|
3
|
+
for (const cell of cols.values()) {
|
|
4
|
+
if (cellCarriesContent(cell))
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
export class UsedExtent {
|
|
10
|
+
#storage;
|
|
11
|
+
#topRowKey = 0;
|
|
12
|
+
#topColumnKey = 0;
|
|
13
|
+
#topDeclaredRow = 0;
|
|
14
|
+
#topDeclaredColumn = 0;
|
|
15
|
+
#stale = false;
|
|
16
|
+
constructor(storage) {
|
|
17
|
+
this.#storage = storage;
|
|
18
|
+
}
|
|
19
|
+
noteCell(row, col) {
|
|
20
|
+
if (row > this.#topRowKey)
|
|
21
|
+
this.#topRowKey = row;
|
|
22
|
+
if (col > this.#topColumnKey)
|
|
23
|
+
this.#topColumnKey = col;
|
|
24
|
+
}
|
|
25
|
+
noteDeclaredRow(row) {
|
|
26
|
+
if (row > this.#topDeclaredRow)
|
|
27
|
+
this.#topDeclaredRow = row;
|
|
28
|
+
}
|
|
29
|
+
noteDeclaredColumn(col) {
|
|
30
|
+
if (col > this.#topDeclaredColumn)
|
|
31
|
+
this.#topDeclaredColumn = col;
|
|
32
|
+
}
|
|
33
|
+
noteMerge(rect) {
|
|
34
|
+
this.noteDeclaredRow(rect.bottom);
|
|
35
|
+
this.noteDeclaredColumn(rect.right);
|
|
36
|
+
}
|
|
37
|
+
invalidate() {
|
|
38
|
+
this.#stale = true;
|
|
39
|
+
}
|
|
40
|
+
reset() {
|
|
41
|
+
this.#topRowKey = 0;
|
|
42
|
+
this.#topColumnKey = 0;
|
|
43
|
+
this.#topDeclaredRow = 0;
|
|
44
|
+
this.#topDeclaredColumn = 0;
|
|
45
|
+
this.#stale = false;
|
|
46
|
+
}
|
|
47
|
+
get lastRow() {
|
|
48
|
+
this.#refresh();
|
|
49
|
+
if (this.#topRowKey <= this.#topDeclaredRow)
|
|
50
|
+
return this.#topDeclaredRow;
|
|
51
|
+
const top = this.#storage.rows.get(this.#topRowKey);
|
|
52
|
+
if (top !== undefined && rowIsUsed(top))
|
|
53
|
+
return this.#topRowKey;
|
|
54
|
+
let last = this.#topDeclaredRow;
|
|
55
|
+
for (const [number, cols] of this.#storage.rows) {
|
|
56
|
+
if (number > last && rowIsUsed(cols))
|
|
57
|
+
last = number;
|
|
58
|
+
}
|
|
59
|
+
return last;
|
|
60
|
+
}
|
|
61
|
+
get lastColumn() {
|
|
62
|
+
this.#refresh();
|
|
63
|
+
if (this.#topColumnKey <= this.#topDeclaredColumn)
|
|
64
|
+
return this.#topDeclaredColumn;
|
|
65
|
+
for (const cols of this.#storage.rows.values()) {
|
|
66
|
+
const cell = cols.get(this.#topColumnKey);
|
|
67
|
+
if (cell !== undefined && cellCarriesContent(cell))
|
|
68
|
+
return this.#topColumnKey;
|
|
69
|
+
}
|
|
70
|
+
let last = this.#topDeclaredColumn;
|
|
71
|
+
for (const cols of this.#storage.rows.values()) {
|
|
72
|
+
for (const [col, cell] of cols) {
|
|
73
|
+
if (col > last && cellCarriesContent(cell))
|
|
74
|
+
last = col;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return last;
|
|
78
|
+
}
|
|
79
|
+
#refresh() {
|
|
80
|
+
if (!this.#stale)
|
|
81
|
+
return;
|
|
82
|
+
this.#stale = false;
|
|
83
|
+
this.#topRowKey = 0;
|
|
84
|
+
this.#topColumnKey = 0;
|
|
85
|
+
this.#topDeclaredRow = 0;
|
|
86
|
+
this.#topDeclaredColumn = 0;
|
|
87
|
+
for (const [number, cols] of this.#storage.rows) {
|
|
88
|
+
if (number > this.#topRowKey)
|
|
89
|
+
this.#topRowKey = number;
|
|
90
|
+
for (const col of cols.keys()) {
|
|
91
|
+
if (col > this.#topColumnKey)
|
|
92
|
+
this.#topColumnKey = col;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (const number of this.#storage.rowProperties.keys())
|
|
96
|
+
this.noteDeclaredRow(number);
|
|
97
|
+
for (const index of this.#storage.columns.keys())
|
|
98
|
+
this.noteDeclaredColumn(index);
|
|
99
|
+
for (const rect of this.#storage.mergeRects)
|
|
100
|
+
this.noteMerge(rect);
|
|
101
|
+
}
|
|
102
|
+
}
|
package/dist/core/workbook.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { type TableStyle } from './table-style.ts';
|
|
|
9
9
|
import { type ThemeColorScheme, type ThemeFontScheme, type ThemeOverrides } from './theme.ts';
|
|
10
10
|
import type { WorkbookProtection } from './workbook-protection.ts';
|
|
11
11
|
import { type DeclaredThemeSchemes } from './workbook-theme.ts';
|
|
12
|
-
import { Worksheet, type WorksheetState } from './worksheet.ts';
|
|
12
|
+
import { type Visibility, Worksheet, type WorksheetState } from './worksheet.ts';
|
|
13
13
|
/**
|
|
14
14
|
* A workbook-level reference to package content the model does not model: a pivot cache
|
|
15
15
|
* (`pivotCacheDefinition`) or a slicer cache (`slicerCache`), preserved verbatim across a round-trip
|
|
@@ -70,7 +70,7 @@ export interface WorkbookView {
|
|
|
70
70
|
/** 0-based index into {@link Workbook.worksheets} of the sheet selected on open. */
|
|
71
71
|
activeTab: number;
|
|
72
72
|
/** Window visibility; omit for a normally visible window. */
|
|
73
|
-
visibility?:
|
|
73
|
+
visibility?: Visibility;
|
|
74
74
|
/** Whether the document window opens minimised; omit for a restored window. */
|
|
75
75
|
minimized?: boolean;
|
|
76
76
|
}
|
|
@@ -5,5 +5,13 @@ export declare class WorksheetComments {
|
|
|
5
5
|
get threads(): readonly CommentThread[];
|
|
6
6
|
add(thread: CommentThread): void;
|
|
7
7
|
at(reference: string): CommentThread | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Re-anchor every conversation through a row or column splice. A thread hangs off exactly one cell,
|
|
10
|
+
* so it moves as a point, not a rectangle, and a thread whose cell the splice deleted is dropped
|
|
11
|
+
* along with its messages. Both halves matter: a cell's legacy note is cell state and travels with
|
|
12
|
+
* the cell, so a thread left behind would put a note on one cell and its conversation on another,
|
|
13
|
+
* a pairing the writer emits and Excel refuses.
|
|
14
|
+
*/
|
|
15
|
+
shift(axis: 'row' | 'col', start: number, count: number, delta: number): void;
|
|
8
16
|
restore(threads: readonly CommentThread[]): void;
|
|
9
17
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { decodeCellRef, encodeAddress } from './address.js';
|
|
2
2
|
import { commentThreadGuid, commentThreadOffset } from './comment-thread.js';
|
|
3
3
|
import { replaceContents } from './containers.js';
|
|
4
|
+
import { isDeletedSpan, shiftIndex } from './grid-shift.js';
|
|
4
5
|
export class WorksheetComments {
|
|
5
6
|
#sheetName;
|
|
6
7
|
#threads = [];
|
|
@@ -42,6 +43,23 @@ export class WorksheetComments {
|
|
|
42
43
|
const anchor = anchorRef(reference);
|
|
43
44
|
return this.#threads.find((thread) => thread.ref === anchor);
|
|
44
45
|
}
|
|
46
|
+
shift(axis, start, count, delta) {
|
|
47
|
+
const survivors = [];
|
|
48
|
+
for (const thread of this.#threads) {
|
|
49
|
+
const { col, row } = decodeCellRef(thread.ref);
|
|
50
|
+
const line = axis === 'row' ? row : col;
|
|
51
|
+
if (isDeletedSpan(line, line, start, count))
|
|
52
|
+
continue;
|
|
53
|
+
const moved = shiftIndex(line, start, count, delta, axis);
|
|
54
|
+
if (moved === line) {
|
|
55
|
+
survivors.push(thread);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const ref = axis === 'row' ? encodeAddress(col, moved) : encodeAddress(moved, row);
|
|
59
|
+
survivors.push({ ...thread, ref });
|
|
60
|
+
}
|
|
61
|
+
replaceContents(this.#threads, survivors);
|
|
62
|
+
}
|
|
45
63
|
restore(threads) {
|
|
46
64
|
replaceContents(this.#threads, threads);
|
|
47
65
|
}
|