@shbernal/ts-xlsx 1.0.3 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/core/address.js +0 -33
- package/dist/core/autofilter.js +0 -9
- package/dist/core/cell.d.ts +10 -0
- package/dist/core/cell.js +4 -123
- package/dist/core/color-resolution.js +0 -60
- package/dist/core/column.d.ts +9 -1
- package/dist/core/column.js +0 -52
- package/dist/core/comment-thread.js +0 -45
- package/dist/core/conditional-formatting-overlay.js +0 -11
- package/dist/core/conditional-formatting.js +0 -10
- package/dist/core/containers.js +0 -15
- package/dist/core/data-validation-overlay.js +0 -25
- package/dist/core/data-validation.js +0 -7
- package/dist/core/date.js +0 -39
- package/dist/core/formula.js +0 -117
- package/dist/core/grid-edits.js +0 -38
- package/dist/core/image.js +0 -25
- package/dist/core/internal.js +0 -27
- package/dist/core/limits.d.ts +30 -0
- package/dist/core/limits.js +2 -0
- package/dist/core/merge.d.ts +18 -0
- package/dist/core/merge.js +22 -7
- package/dist/core/modern-functions.js +0 -17
- package/dist/core/page-setup.js +0 -5
- package/dist/core/pivot-table.d.ts +1 -1
- package/dist/core/pivot-table.js +0 -44
- package/dist/core/preserved.js +0 -4
- package/dist/core/protection.js +0 -26
- package/dist/core/range.js +0 -112
- package/dist/core/row-input.d.ts +24 -0
- package/dist/core/row-input.js +29 -0
- package/dist/core/row.d.ts +9 -1
- package/dist/core/row.js +0 -58
- package/dist/core/style.js +0 -26
- package/dist/core/table-style.js +0 -28
- package/dist/core/table.js +0 -97
- package/dist/core/text-metrics.d.ts +20 -0
- package/dist/core/text-metrics.js +10 -0
- package/dist/core/theme.js +0 -85
- package/dist/core/value.d.ts +40 -0
- package/dist/core/value.js +30 -48
- package/dist/core/workbook-protection.js +0 -13
- package/dist/core/workbook.d.ts +13 -0
- package/dist/core/workbook.js +11 -368
- package/dist/core/worksheet-model.js +2 -23
- package/dist/core/worksheet.d.ts +22 -0
- package/dist/core/worksheet.js +14 -485
- package/dist/customui/errors.js +0 -10
- package/dist/customui/index.js +0 -5
- package/dist/customui/ribbon.js +0 -43
- package/dist/entries/core.d.ts +3 -1
- package/dist/entries/core.js +3 -7
- package/dist/entries/csv.js +0 -4
- package/dist/entries/customui.js +0 -2
- package/dist/entries/errors.js +0 -13
- package/dist/entries/vba.js +0 -5
- package/dist/entries/xlsb.js +0 -6
- package/dist/entries/xlsx.js +0 -13
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +5 -56
- package/dist/index.js +0 -12
- package/dist/io/csv/read.js +0 -18
- package/dist/io/csv/write.js +5 -35
- package/dist/io/opc/errors.js +0 -30
- package/dist/io/opc/inflate.js +0 -35
- package/dist/io/opc/namespaces.js +0 -15
- package/dist/io/opc/part-paths.js +0 -11
- package/dist/io/opc/read-opc.js +0 -31
- package/dist/io/opc/read-options.js +0 -6
- package/dist/io/opc/rels.js +0 -13
- package/dist/io/opc/sniff-format.js +0 -50
- package/dist/io/style/xf-style.js +0 -31
- package/dist/io/xlsb/errors.js +0 -10
- package/dist/io/xlsb/formula.js +1 -99
- package/dist/io/xlsb/primitives.js +1 -83
- package/dist/io/xlsb/ptg-functions.js +0 -34
- package/dist/io/xlsb/read-shared-strings.js +0 -7
- package/dist/io/xlsb/read-styles.js +3 -66
- package/dist/io/xlsb/read-worksheet.js +4 -87
- package/dist/io/xlsb/read.js +2 -60
- package/dist/io/xlsb/record-stream.js +0 -28
- package/dist/io/xlsb/record-types.js +0 -19
- package/dist/io/xlsx/cell-accumulator.js +0 -32
- package/dist/io/xlsx/cell-value.js +0 -29
- package/dist/io/xlsx/color-xml.d.ts +6 -0
- package/dist/io/xlsx/color-xml.js +42 -0
- package/dist/io/xlsx/comments.js +0 -84
- package/dist/io/xlsx/conditional-formatting.d.ts +1 -1
- package/dist/io/xlsx/conditional-formatting.js +1 -97
- package/dist/io/xlsx/data-validation.js +0 -56
- package/dist/io/xlsx/edit-vba.js +0 -54
- package/dist/io/xlsx/errors.js +0 -12
- package/dist/io/xlsx/hyperlinks.js +0 -40
- package/dist/io/xlsx/images.js +0 -32
- package/dist/io/xlsx/namespaces.js +0 -42
- package/dist/io/xlsx/package-plan.js +0 -47
- package/dist/io/xlsx/pivot.js +0 -22
- package/dist/io/xlsx/{pivot-read.js → read-pivot.js} +0 -30
- package/dist/io/xlsx/read-rows.js +1 -83
- package/dist/io/xlsx/{shared-strings-read.js → read-shared-strings.js} +0 -11
- package/dist/io/xlsx/read-styles.d.ts +25 -1
- package/dist/io/xlsx/read-styles.js +61 -108
- package/dist/io/xlsx/read-worksheet.js +1 -88
- package/dist/io/xlsx/read.js +3 -211
- package/dist/io/xlsx/relationships.js +0 -11
- package/dist/io/xlsx/rich-runs.js +0 -16
- package/dist/io/xlsx/rich-text.js +0 -12
- package/dist/io/xlsx/shared-formulas.js +0 -7
- package/dist/io/xlsx/shared-strings.js +0 -21
- package/dist/io/xlsx/sheet-properties.js +1 -64
- package/dist/io/xlsx/styles.d.ts +1 -29
- package/dist/io/xlsx/styles.js +2 -366
- package/dist/io/xlsx/tables.js +5 -52
- package/dist/io/xlsx/threaded-comments.js +0 -103
- package/dist/io/xlsx/workbook-xml.js +0 -94
- package/dist/io/xlsx/worksheet-xml.js +0 -174
- package/dist/io/xlsx/write-stream.js +0 -137
- package/dist/io/xlsx/write.js +1 -134
- package/dist/io/xlsx/x14-ext.js +0 -8
- package/dist/vba/cfb-writer.js +12 -59
- package/dist/vba/cfb.js +2 -32
- package/dist/vba/codepage.js +1 -23
- package/dist/vba/errors.js +0 -15
- package/dist/vba/index.js +0 -10
- package/dist/vba/ms-ovba.js +3 -46
- package/dist/vba/project-editor.js +4 -89
- package/dist/vba/project.js +2 -34
- package/dist/vba/vba-encoding.js +1 -17
- package/dist/xml/errors.js +0 -10
- package/dist/xml/xml-read.js +3 -82
- package/dist/xml/xml.js +0 -39
- package/package.json +9 -10
- package/skills/ts-xlsx-upstream/SKILL.md +178 -0
- /package/dist/io/xlsx/{pivot-read.d.ts → read-pivot.d.ts} +0 -0
- /package/dist/io/xlsx/{shared-strings-read.d.ts → read-shared-strings.d.ts} +0 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tallest row Excel accepts being set to, in points: it refuses 409.6 and takes 409.5. A row
|
|
3
|
+
* asked to hold more wrapped text than this cannot grow to fit it, and the overflow is simply not
|
|
4
|
+
* shown.
|
|
5
|
+
*
|
|
6
|
+
* A file may state more, and stating more loses the value rather than the file. Excel opens such a
|
|
7
|
+
* package without complaint and silently clamps the row — to 409.6, a tick *above* what it lets
|
|
8
|
+
* you assign, being 8192 twentieths of a point and so the width of the field it is read into — and
|
|
9
|
+
* writes 409.6 back on its next save. Check against this constant to keep a stated height from
|
|
10
|
+
* quietly becoming a different one.
|
|
11
|
+
*/
|
|
12
|
+
export declare const MAX_ROW_HEIGHT = 409.5;
|
|
13
|
+
/**
|
|
14
|
+
* The widest column Excel accepts being set to, in character units of the workbook's default font.
|
|
15
|
+
* Excel refuses 255.4, so unlike the row-height ceiling this one is exactly integral.
|
|
16
|
+
*
|
|
17
|
+
* It is also the weaker of the two ceilings: it binds assignment only, and not a file at all.
|
|
18
|
+
* Excel honours a `width` of 1000 read from a package, renders the column at it, and round-trips
|
|
19
|
+
* it verbatim through its own save — where an over-limit row height is clamped away. So a width
|
|
20
|
+
* above this is a column no Excel user could have produced by dragging, not a value at risk.
|
|
21
|
+
*
|
|
22
|
+
* Character units, not points or pixels: a width is a count of digits of the default font's
|
|
23
|
+
* *maximum digit width*, which is why there is no companion `DEFAULT_COLUMN_WIDTH` constant here.
|
|
24
|
+
* The width a column takes when it states none is a function of that font — the familiar 8.43 holds
|
|
25
|
+
* for Calibri 11 and not for a workbook whose normal style says otherwise (Excel reports 8.09 for
|
|
26
|
+
* Aptos Narrow 11). `sheet.properties.defaultColWidth` is what a file declares, and
|
|
27
|
+
* docs/knowledge/specs/default-font-must-not-be-assumed-for-column-widths.md is why assuming a
|
|
28
|
+
* value for it is a bug rather than a shortcut.
|
|
29
|
+
*/
|
|
30
|
+
export declare const MAX_COLUMN_WIDTH = 255;
|
package/dist/core/merge.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Cell } from './cell.ts';
|
|
1
2
|
/** A merged region as inclusive 1-based grid bounds. */
|
|
2
3
|
export interface MergeRect {
|
|
3
4
|
readonly top: number;
|
|
@@ -7,6 +8,23 @@ export interface MergeRect {
|
|
|
7
8
|
}
|
|
8
9
|
/** Whether two inclusive grid rectangles share at least one cell. */
|
|
9
10
|
export declare function rectsOverlap(a: MergeRect, b: MergeRect): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Resolve a position to the master (top-left) of the merged region covering it, or to itself when no
|
|
13
|
+
* region does. First covering region wins; `Worksheet.mergeCells` rejects overlaps, so at most one
|
|
14
|
+
* region ever applies. Only fully-bounded rects participate — an unbounded whole-row/column merge
|
|
15
|
+
* carries no rect and so resolves nothing.
|
|
16
|
+
*/
|
|
17
|
+
export declare function masterOf(rects: readonly MergeRect[], row: number, col: number): {
|
|
18
|
+
row: number;
|
|
19
|
+
col: number;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Drop any value already sitting in a merge's covered non-anchor cells, keeping only the top-left
|
|
23
|
+
* anchor — the collapse Excel performs on merge. A leftover covered value would serialise as a
|
|
24
|
+
* populated `<c>` under the range's `<mergeCell>` ref, the geometry that trips Excel's repair prompt.
|
|
25
|
+
* Styles are untouched: a border spanning the merged region rides the covered cells.
|
|
26
|
+
*/
|
|
27
|
+
export declare function clearCoveredValues(rows: Map<number, Map<number, Cell>>, rect: MergeRect): void;
|
|
10
28
|
/** Decode an OOXML `sqref` (one or more space-separated ranges) into containment rectangles. A whole
|
|
11
29
|
* column or row leaves one axis unbounded, so its missing edges open to `Infinity` rather than
|
|
12
30
|
* clamping — a cell anywhere down the column still resolves inside it. */
|
package/dist/core/merge.js
CHANGED
|
@@ -1,14 +1,29 @@
|
|
|
1
|
-
// Inclusive grid rectangles and the geometry a worksheet uses to reason about merged regions and the
|
|
2
|
-
// `sqref` ranges that overlays (data validations, conditional formats) apply to: overlap detection and
|
|
3
|
-
// decoding an OOXML `sqref` into containment rectangles.
|
|
4
1
|
import { decodeRange } from './address.js';
|
|
5
|
-
/** Whether two inclusive grid rectangles share at least one cell. */
|
|
6
2
|
export function rectsOverlap(a, b) {
|
|
7
3
|
return a.left <= b.right && b.left <= a.right && a.top <= b.bottom && b.top <= a.bottom;
|
|
8
4
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
export function masterOf(rects, row, col) {
|
|
6
|
+
for (const rect of rects) {
|
|
7
|
+
if (row >= rect.top && row <= rect.bottom && col >= rect.left && col <= rect.right) {
|
|
8
|
+
return { row: rect.top, col: rect.left };
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return { row, col };
|
|
12
|
+
}
|
|
13
|
+
export function clearCoveredValues(rows, rect) {
|
|
14
|
+
for (let row = rect.top; row <= rect.bottom; row++) {
|
|
15
|
+
const cols = rows.get(row);
|
|
16
|
+
if (cols === undefined)
|
|
17
|
+
continue;
|
|
18
|
+
for (let col = rect.left; col <= rect.right; col++) {
|
|
19
|
+
if (row === rect.top && col === rect.left)
|
|
20
|
+
continue;
|
|
21
|
+
const covered = cols.get(col);
|
|
22
|
+
if (covered !== undefined)
|
|
23
|
+
covered.value = null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
12
27
|
export function decodeSqrefRects(sqref) {
|
|
13
28
|
const rects = [];
|
|
14
29
|
for (const part of sqref.split(/\s+/)) {
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
// The post-2007 functions Excel persists with an `_xlfn.` prefix, keyed by their uppercased name.
|
|
2
|
-
// OOXML froze its function grammar around Excel 2007; everything Microsoft added since is unknown to
|
|
3
|
-
// older readers under its bare name, so the writer stores it prefixed and the reader strips the prefix
|
|
4
|
-
// back — the mangling in `formula.ts` is the single place that applies and removes it. That mangler
|
|
5
|
-
// treats '.' as part of a function name, so both the plain modern functions and the dotted 2010
|
|
6
|
-
// statistical rename family below are matched as whole names and prefixed.
|
|
7
1
|
export const MODERN_FUNCTIONS = new Set([
|
|
8
|
-
// Dynamic arrays (Excel 365)
|
|
9
2
|
'FILTER',
|
|
10
3
|
'SORT',
|
|
11
4
|
'SORTBY',
|
|
@@ -14,7 +7,6 @@ export const MODERN_FUNCTIONS = new Set([
|
|
|
14
7
|
'RANDARRAY',
|
|
15
8
|
'XLOOKUP',
|
|
16
9
|
'XMATCH',
|
|
17
|
-
// LAMBDA and its helpers
|
|
18
10
|
'LAMBDA',
|
|
19
11
|
'LET',
|
|
20
12
|
'BYROW',
|
|
@@ -24,7 +16,6 @@ export const MODERN_FUNCTIONS = new Set([
|
|
|
24
16
|
'REDUCE',
|
|
25
17
|
'SCAN',
|
|
26
18
|
'ISOMITTED',
|
|
27
|
-
// Array shaping (Excel 365)
|
|
28
19
|
'VSTACK',
|
|
29
20
|
'HSTACK',
|
|
30
21
|
'TOROW',
|
|
@@ -36,7 +27,6 @@ export const MODERN_FUNCTIONS = new Set([
|
|
|
36
27
|
'EXPAND',
|
|
37
28
|
'CHOOSEROWS',
|
|
38
29
|
'CHOOSECOLS',
|
|
39
|
-
// Text (Excel 2019 / 365)
|
|
40
30
|
'TEXTJOIN',
|
|
41
31
|
'CONCAT',
|
|
42
32
|
'TEXTBEFORE',
|
|
@@ -44,14 +34,10 @@ export const MODERN_FUNCTIONS = new Set([
|
|
|
44
34
|
'TEXTSPLIT',
|
|
45
35
|
'ARRAYTOTEXT',
|
|
46
36
|
'VALUETOTEXT',
|
|
47
|
-
// Logical and conditional aggregation (Excel 2016 / 2019)
|
|
48
37
|
'IFS',
|
|
49
38
|
'SWITCH',
|
|
50
39
|
'MAXIFS',
|
|
51
40
|
'MINIFS',
|
|
52
|
-
// Other bare-name functions added after the frozen grammar (Excel 2010 / 2013) — trigonometric,
|
|
53
|
-
// bitwise, engineering, information, and math/financial additions. Their names carry no '.', so
|
|
54
|
-
// they need no tokenizer work; they simply have to be recognised as modern to earn the prefix.
|
|
55
41
|
'AGGREGATE',
|
|
56
42
|
'ACOT',
|
|
57
43
|
'ACOTH',
|
|
@@ -99,9 +85,6 @@ export const MODERN_FUNCTIONS = new Set([
|
|
|
99
85
|
'XOR',
|
|
100
86
|
'PDURATION',
|
|
101
87
|
'RRI',
|
|
102
|
-
// The Excel 2010 statistical-consistency rename family and the handful of other post-2007
|
|
103
|
-
// functions whose canonical names contain a '.'. They carry the same `_xlfn.` prefix; the whole
|
|
104
|
-
// dotted name is stored, e.g. `_xlfn.NORM.DIST`, `_xlfn.T.DIST.2T`.
|
|
105
88
|
'BETA.DIST',
|
|
106
89
|
'BETA.INV',
|
|
107
90
|
'BINOM.DIST',
|
package/dist/core/page-setup.js
CHANGED
|
@@ -1,6 +1 @@
|
|
|
1
|
-
// The print/page-layout data shapes a worksheet carries: how it scales and orients on paper, which
|
|
2
|
-
// print toggles are set, where manual page breaks fall, the margins, and the header/footer text. Each
|
|
3
|
-
// is a pure data shape mapping onto an OOXML print element (`<pageSetup>`, `<printOptions>`, `<brk>`,
|
|
4
|
-
// `<pageMargins>`, `<headerFooter>`); the model stores only what an author or source file set, so an
|
|
5
|
-
// unset field is omitted and a round-trip never fabricates one.
|
|
6
1
|
export {};
|
|
@@ -29,7 +29,7 @@ export interface ParsedPivotSource {
|
|
|
29
29
|
readonly ref: string;
|
|
30
30
|
}
|
|
31
31
|
/** The semantic model reconstructed from a loaded pivot's `pivotTableDefinition` and its
|
|
32
|
-
* `pivotCacheDefinition` (see `io/xlsx/pivot
|
|
32
|
+
* `pivotCacheDefinition` (see `io/xlsx/read-pivot.ts`). Field roles are indices into {@link fields};
|
|
33
33
|
* {@link metric} is the aggregation the value field applies. This mirrors the authoring model's shape
|
|
34
34
|
* without requiring the source sheet it was built from, so a pivot loaded from a package is
|
|
35
35
|
* inspectable data rather than an opaque preserved blob. It is a read-only view: the writer emits a
|
package/dist/core/pivot-table.js
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
// A pivot table authored from a model.
|
|
2
|
-
//
|
|
3
|
-
// A pivot summarises a source range: its distinct field values become row/column axes and a value
|
|
4
|
-
// field is aggregated across them. OOXML splits that into three parts — a `pivotCacheDefinition`
|
|
5
|
-
// (the field catalogue), a `pivotCacheRecords` (a copy of the source rows, with axis-field cells
|
|
6
|
-
// swapped for indices into the catalogue), and a `pivotTableDefinition` (the layout on the
|
|
7
|
-
// destination sheet). This module owns the *semantic* computation of all three; the OOXML rendering
|
|
8
|
-
// lives in `io/xlsx/pivot.ts`.
|
|
9
|
-
//
|
|
10
|
-
// The source data is captured when the pivot is added — the model reads the source sheet's cells
|
|
11
|
-
// once, here, so the pivot is a stable snapshot independent of later edits to the source.
|
|
12
1
|
import { AuthoringError, InternalError } from '../errors.js';
|
|
13
2
|
import { encodeAddress } from './address.js';
|
|
14
3
|
import { isErrorValue, isFormulaValue, isHyperlinkValue, isRichTextValue, isSharedFormulaValue, richTextToPlain, } from './value.js';
|
|
@@ -25,36 +14,21 @@ const PIVOT_METRICS = new Set([
|
|
|
25
14
|
'var',
|
|
26
15
|
'varp',
|
|
27
16
|
]);
|
|
28
|
-
/** Narrow a raw `subtotal` attribute (or any string) to a known {@link PivotMetric}. */
|
|
29
17
|
export function isPivotMetric(value) {
|
|
30
18
|
return PIVOT_METRICS.has(value);
|
|
31
19
|
}
|
|
32
|
-
/** Map an OOXML `<dataField subtotal="…">` value back to its metric. The attribute is absent for
|
|
33
|
-
* `sum` (Excel's implicit default), so `undefined` reads as `sum`; an unrecognised value also reads
|
|
34
|
-
* as `sum` rather than throwing, because reconstructing an existing file is a lenient operation —
|
|
35
|
-
* the strict rejection of unknown metrics belongs on the authoring path, not the read path. */
|
|
36
20
|
export function pivotMetricFromSubtotal(subtotal) {
|
|
37
21
|
if (subtotal === undefined)
|
|
38
22
|
return 'sum';
|
|
39
23
|
return isPivotMetric(subtotal) ? subtotal : 'sum';
|
|
40
24
|
}
|
|
41
25
|
const BLANK = { kind: 'blank' };
|
|
42
|
-
/**
|
|
43
|
-
* A pivot table built over a source sheet's data. Construction reads the source once and computes
|
|
44
|
-
* the full cache (fields + records) and the axis-field wiring the renderer needs; nothing here
|
|
45
|
-
* touches XML.
|
|
46
|
-
*
|
|
47
|
-
* Supported shape: exactly one value field aggregated by `sum`, at least one row field and one
|
|
48
|
-
* column field. An unsupported request throws at authoring time rather than emitting a corrupt file.
|
|
49
|
-
*/
|
|
50
26
|
export class PivotTable {
|
|
51
27
|
metric;
|
|
52
28
|
sourceSheetName;
|
|
53
|
-
/** The `A1:C4` source range: the header row through the last data row, across the field columns. */
|
|
54
29
|
sourceRef;
|
|
55
30
|
cacheFields;
|
|
56
31
|
records;
|
|
57
|
-
/** Indices into {@link cacheFields} of the row-axis, column-axis, and value fields. */
|
|
58
32
|
rowFields;
|
|
59
33
|
columnFields;
|
|
60
34
|
valueField;
|
|
@@ -70,7 +44,6 @@ export class PivotTable {
|
|
|
70
44
|
if (columnCount < 1 || lastRow < 2) {
|
|
71
45
|
throw new AuthoringError('a pivot source needs a header row and at least one data row');
|
|
72
46
|
}
|
|
73
|
-
// Every non-blank header cell in row 1 defines a field, in ascending column order.
|
|
74
47
|
const fields = [];
|
|
75
48
|
for (let col = 1; col <= columnCount; col++) {
|
|
76
49
|
const name = textOf(scalarOf(source.getCell(encodeAddress(col, 1)).value));
|
|
@@ -98,7 +71,6 @@ export class PivotTable {
|
|
|
98
71
|
this.columnFields = options.columns.map((name) => resolve('column', name));
|
|
99
72
|
this.valueField = resolve('value', valueName);
|
|
100
73
|
const axisFields = new Set([...this.rowFields, ...this.columnFields]);
|
|
101
|
-
// fields is non-empty (guarded above); the source span runs from its first to its last column.
|
|
102
74
|
const firstField = fields[0];
|
|
103
75
|
const lastField = fields[fields.length - 1];
|
|
104
76
|
if (firstField === undefined || lastField === undefined) {
|
|
@@ -106,8 +78,6 @@ export class PivotTable {
|
|
|
106
78
|
}
|
|
107
79
|
this.sourceSheetName = source.name;
|
|
108
80
|
this.sourceRef = `${encodeAddress(firstField.col, 1)}:${encodeAddress(lastField.col, lastRow)}`;
|
|
109
|
-
// Read the source body once, field by field, so the same scan feeds both the shared-items
|
|
110
|
-
// catalogues and the records that reference them.
|
|
111
81
|
const dataRowCount = lastRow - 1;
|
|
112
82
|
const columnScalars = fields.map((field) => {
|
|
113
83
|
const scalars = [];
|
|
@@ -146,7 +116,6 @@ export class PivotTable {
|
|
|
146
116
|
const catalogue = catalogues[fieldIndex];
|
|
147
117
|
if (!catalogue)
|
|
148
118
|
return scalar;
|
|
149
|
-
// Every scalar was catalogued in the shared-items pass above, so this always hits.
|
|
150
119
|
const index = catalogue.get(itemKey(scalar));
|
|
151
120
|
if (index === undefined)
|
|
152
121
|
throw new InternalError('pivot record references an uncatalogued item');
|
|
@@ -155,7 +124,6 @@ export class PivotTable {
|
|
|
155
124
|
}
|
|
156
125
|
this.records = records;
|
|
157
126
|
}
|
|
158
|
-
/** The value field's header name, used to label the aggregated data column ("Sum of Amount"). */
|
|
159
127
|
get valueFieldName() {
|
|
160
128
|
const field = this.cacheFields[this.valueField];
|
|
161
129
|
if (field === undefined) {
|
|
@@ -165,9 +133,6 @@ export class PivotTable {
|
|
|
165
133
|
return field.name;
|
|
166
134
|
}
|
|
167
135
|
}
|
|
168
|
-
// columnScalars[fieldIndex] is always present: it was built by mapping the same `fields` array this
|
|
169
|
-
// index is drawn from. Centralised here so the invariant is asserted once rather than cast away at
|
|
170
|
-
// each of its two call sites.
|
|
171
136
|
function scalarsForField(columnScalars, fieldIndex) {
|
|
172
137
|
const scalars = columnScalars[fieldIndex];
|
|
173
138
|
if (scalars === undefined) {
|
|
@@ -175,7 +140,6 @@ function scalarsForField(columnScalars, fieldIndex) {
|
|
|
175
140
|
}
|
|
176
141
|
return scalars;
|
|
177
142
|
}
|
|
178
|
-
/** A stable dedup key for a shared item: kind-tagged so the number `1` and the string `"1"` differ. */
|
|
179
143
|
function itemKey(item) {
|
|
180
144
|
switch (item.kind) {
|
|
181
145
|
case 'string':
|
|
@@ -186,8 +150,6 @@ function itemKey(item) {
|
|
|
186
150
|
return 'b';
|
|
187
151
|
}
|
|
188
152
|
}
|
|
189
|
-
/** The numeric summary for an inline field, or null when any present value is non-numeric (a string
|
|
190
|
-
* present means the field is not a pure numeric column; blanks alone do not disqualify it). */
|
|
191
153
|
function numericSummary(scalars) {
|
|
192
154
|
let min = Infinity;
|
|
193
155
|
let max = -Infinity;
|
|
@@ -208,15 +170,9 @@ function numericSummary(scalars) {
|
|
|
208
170
|
}
|
|
209
171
|
return sawNumber ? { allInteger, min, max } : null;
|
|
210
172
|
}
|
|
211
|
-
/** The string form of a shared item, used for header names (a blank header contributes no field). */
|
|
212
173
|
function textOf(item) {
|
|
213
174
|
return item.kind === 'blank' ? '' : String(item.value);
|
|
214
175
|
}
|
|
215
|
-
/**
|
|
216
|
-
* Reduce any cell value to the scalar a pivot cache can hold: a number, a string, or a blank. Only
|
|
217
|
-
* finite numbers stay numeric (a NaN would corrupt the cache); every other kind is flattened to its
|
|
218
|
-
* displayed text so hostile or exotic source content can never throw or leak an object into the XML.
|
|
219
|
-
*/
|
|
220
176
|
function scalarOf(value) {
|
|
221
177
|
if (value === null)
|
|
222
178
|
return BLANK;
|
package/dist/core/preserved.js
CHANGED
|
@@ -1,5 +1 @@
|
|
|
1
|
-
// Package content the model does not interpret, captured verbatim so a round-trip re-emits it intact.
|
|
2
|
-
// A preserved reference names a worksheet- or workbook-level relationship (a chart, vector drawing,
|
|
3
|
-
// slicer, pivot table) the model does not model; its transitive closure of parts is held as raw bytes
|
|
4
|
-
// with their content types and rewired relationships, and re-emitted unchanged on write.
|
|
5
1
|
export {};
|
package/dist/core/protection.js
CHANGED
|
@@ -1,22 +1,4 @@
|
|
|
1
|
-
// Worksheet-level protection: the `<sheetProtection>` state that makes the per-cell
|
|
2
|
-
// `locked`/`hidden` flags enforceable. A cell's protection does nothing until the sheet
|
|
3
|
-
// itself is protected; this module models that sheet-level switch and the optional
|
|
4
|
-
// password credential that guards lifting it.
|
|
5
|
-
//
|
|
6
|
-
// The option surface is stated in the AUTHOR's terms — each flag answers "may a user do
|
|
7
|
-
// this while the sheet is protected?" (`sort: true` = sorting stays available). OOXML
|
|
8
|
-
// encodes the inverse ("1" LOCKS an operation, "0"/omission PERMITS it) and its per-
|
|
9
|
-
// attribute defaults differ; that encoding table is {@link SHEET_PROTECTION_FLAGS} below,
|
|
10
|
-
// shared by the writer and reader, while the translation that consumes it lives in the io layer.
|
|
11
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
12
|
-
/**
|
|
13
|
-
* The OOXML encoding table for the protection flags: each `<sheetProtection>` attribute paired
|
|
14
|
-
* with whether that operation is *forbidden by default* once a sheet is protected. Both directions
|
|
15
|
-
* key off this one list — the writer turns an author allow-flag into an attribute (omitting values
|
|
16
|
-
* equal to the default), the reader turns an attribute back into an allow-flag — so serialization
|
|
17
|
-
* and deserialization can never fall out of step. Most editing operations default to forbidden
|
|
18
|
-
* under protection; selecting cells and the object/scenario operations default to permitted.
|
|
19
|
-
*/
|
|
20
2
|
export const SHEET_PROTECTION_FLAGS = [
|
|
21
3
|
{ key: 'formatCells', defaultForbidden: true },
|
|
22
4
|
{ key: 'formatColumns', defaultForbidden: true },
|
|
@@ -34,18 +16,10 @@ export const SHEET_PROTECTION_FLAGS = [
|
|
|
34
16
|
{ key: 'selectLockedCells', defaultForbidden: false },
|
|
35
17
|
{ key: 'selectUnlockedCells', defaultForbidden: false },
|
|
36
18
|
];
|
|
37
|
-
// OOXML's agile hashing (ECMA-376 / MS-OFFCRYPTO): the password is UTF-16LE, prefixed with
|
|
38
|
-
// the salt for the first hash, then re-hashed `spinCount` times with a little-endian uint32
|
|
39
|
-
// iteration counter mixed in. SHA-512 is the modern choice Excel writes.
|
|
40
19
|
const ALGORITHM_NAME = 'SHA-512';
|
|
41
20
|
const HASH = 'sha512';
|
|
42
21
|
const DEFAULT_SPIN_COUNT = 100000;
|
|
43
22
|
const SALT_BYTES = 16;
|
|
44
|
-
/**
|
|
45
|
-
* Derive a fresh {@link SheetProtectionCredential} for a password. Each call generates a new
|
|
46
|
-
* random salt, so protecting two sheets with the same password yields different credentials —
|
|
47
|
-
* the salt is real randomness, not a stub.
|
|
48
|
-
*/
|
|
49
23
|
export function deriveCredential(password, spinCount = DEFAULT_SPIN_COUNT) {
|
|
50
24
|
const salt = randomBytes(SALT_BYTES);
|
|
51
25
|
const secret = Buffer.from(password, 'utf16le');
|
package/dist/core/range.js
CHANGED
|
@@ -1,44 +1,12 @@
|
|
|
1
|
-
// A handle on a rectangular block of cells, reached by an A1 range reference or by its corners.
|
|
2
|
-
//
|
|
3
|
-
// The third axis handle, after `Row` and `Column`, and the same contract as both: a live view over
|
|
4
|
-
// the worksheet's stores, bounds fixed at construction, nothing materialised on read. See
|
|
5
|
-
// `core/row.ts` for why these are handles rather than records.
|
|
6
|
-
//
|
|
7
|
-
// **The rectangle is bounded, always.** `A:A` and `1:1` are legal range references and are rejected
|
|
8
|
-
// here, because a whole-axis "range" is not this shape of thing: styling one means stamping a
|
|
9
|
-
// million cells, whereas OOXML expresses it natively as a single `<col style>` or `<row s>` — which
|
|
10
|
-
// is what `getColumn`/`getRow` already write, in constant space. Refusing them is what lets a range
|
|
11
|
-
// materialise its cells eagerly without a cost cliff hiding behind an innocuous-looking call.
|
|
12
1
|
import { decodeRange, encodeAddress, MAX_COLUMN, MAX_ROW } from './address.js';
|
|
13
2
|
import { applyCellStyle } from './cell.js';
|
|
14
3
|
import { CELL_STYLE_FACETS, } from './style.js';
|
|
15
|
-
/**
|
|
16
|
-
* A rectangular block of a worksheet's cells: `sheet.getRange('B2:D5')`.
|
|
17
|
-
*
|
|
18
|
-
* Cheap and stateless — constructing one creates no cells and does not extend the used range.
|
|
19
|
-
* {@link addresses} walks the block without materialising anything; {@link cells} reports only what
|
|
20
|
-
* already exists.
|
|
21
|
-
*
|
|
22
|
-
* Bounds are **inclusive first/last**, never start-and-count. That is the convention every
|
|
23
|
-
* range-shaped accessor in this library follows, so the three axes cannot disagree about what a
|
|
24
|
-
* pair of numbers means.
|
|
25
|
-
*/
|
|
26
4
|
export class Range {
|
|
27
5
|
#sheet;
|
|
28
|
-
/** 1-based row of the block's top edge. Fixed for this handle's lifetime. */
|
|
29
6
|
top;
|
|
30
|
-
/** 1-based column of the block's left edge. Fixed for this handle's lifetime. */
|
|
31
7
|
left;
|
|
32
|
-
/** 1-based row of the block's bottom edge, inclusive. */
|
|
33
8
|
bottom;
|
|
34
|
-
/** 1-based column of the block's right edge, inclusive. */
|
|
35
9
|
right;
|
|
36
|
-
/**
|
|
37
|
-
* Build a handle from inclusive corners, in any order — `(5, 4, 2, 2)` and `(2, 2, 5, 4)` name the
|
|
38
|
-
* same block, exactly as `D5:B2` and `B2:D5` do. Prefer {@link Worksheet.getRange}.
|
|
39
|
-
*
|
|
40
|
-
* @throws {RangeError} if a corner is not a positive integer or falls outside the sheet's bounds.
|
|
41
|
-
*/
|
|
42
10
|
constructor(sheet, top, left, bottom, right) {
|
|
43
11
|
checkBound('row', top, MAX_ROW);
|
|
44
12
|
checkBound('row', bottom, MAX_ROW);
|
|
@@ -50,35 +18,24 @@ export class Range {
|
|
|
50
18
|
this.left = Math.min(left, right);
|
|
51
19
|
this.right = Math.max(left, right);
|
|
52
20
|
}
|
|
53
|
-
/** The worksheet this block belongs to. */
|
|
54
21
|
get sheet() {
|
|
55
22
|
return this.#sheet;
|
|
56
23
|
}
|
|
57
|
-
/** Canonical `tl:br` A1 form — `"B2:D5"`. A one-cell block still reads as `"B2:B2"`. */
|
|
58
24
|
get address() {
|
|
59
25
|
return `${encodeAddress(this.left, this.top)}:${encodeAddress(this.right, this.bottom)}`;
|
|
60
26
|
}
|
|
61
|
-
/** Rows spanned, inclusive of both edges. */
|
|
62
27
|
get rowCount() {
|
|
63
28
|
return this.bottom - this.top + 1;
|
|
64
29
|
}
|
|
65
|
-
/** Columns spanned, inclusive of both edges. */
|
|
66
30
|
get columnCount() {
|
|
67
31
|
return this.right - this.left + 1;
|
|
68
32
|
}
|
|
69
|
-
/** Cells the block covers — `rowCount * columnCount`, whether or not they exist yet. */
|
|
70
33
|
get cellCount() {
|
|
71
34
|
return this.rowCount * this.columnCount;
|
|
72
35
|
}
|
|
73
|
-
/** Whether a 1-based position falls inside the block. */
|
|
74
36
|
contains(row, col) {
|
|
75
37
|
return row >= this.top && row <= this.bottom && col >= this.left && col <= this.right;
|
|
76
38
|
}
|
|
77
|
-
/**
|
|
78
|
-
* Every address the block covers, row-major (`B2`, `C2`, `D2`, `B3`, …). Materialises nothing, so
|
|
79
|
-
* this is the cheap way to walk a large block — and, being a generator, it can be abandoned
|
|
80
|
-
* part-way without having built the whole list.
|
|
81
|
-
*/
|
|
82
39
|
*addresses() {
|
|
83
40
|
for (let row = this.top; row <= this.bottom; row++) {
|
|
84
41
|
for (let col = this.left; col <= this.right; col++) {
|
|
@@ -86,11 +43,6 @@ export class Range {
|
|
|
86
43
|
}
|
|
87
44
|
}
|
|
88
45
|
}
|
|
89
|
-
/**
|
|
90
|
-
* The block's **materialised** cells, row-major. Sparse: a position nothing has ever written to is
|
|
91
|
-
* simply absent, which is what distinguishes "never written" from a cell holding `null`. Reading
|
|
92
|
-
* this creates nothing — mirroring {@link Column.cells}.
|
|
93
|
-
*/
|
|
94
46
|
get cells() {
|
|
95
47
|
const cells = [];
|
|
96
48
|
for (let row = this.top; row <= this.bottom; row++) {
|
|
@@ -101,25 +53,6 @@ export class Range {
|
|
|
101
53
|
}
|
|
102
54
|
return cells;
|
|
103
55
|
}
|
|
104
|
-
/**
|
|
105
|
-
* The block's style, facet by facet — the counterpart of {@link Cell.style} over a rectangle, with
|
|
106
|
-
* the same semantics in both directions.
|
|
107
|
-
*
|
|
108
|
-
* **Reading** reports a facet only when *every* position in the block carries a structurally
|
|
109
|
-
* identical one, and `undefined` when they differ or any position is still empty. A block styled
|
|
110
|
-
* through this handle therefore reads back what was written; a block whose cells disagree says so
|
|
111
|
-
* rather than picking a corner's answer and passing it off as the whole.
|
|
112
|
-
*
|
|
113
|
-
* **Writing** lays each facet the payload names onto every cell, leaving facets it omits untouched
|
|
114
|
-
* — exactly what `cell.style = {...}` does, so this composes with prior styling instead of clearing
|
|
115
|
-
* it. Use {@link clearStyle} first for a wholesale replace.
|
|
116
|
-
*
|
|
117
|
-
* Writing **materialises** every position in the block, because a styled-but-valueless cell is the
|
|
118
|
-
* only way an empty cell renders with a fill: skipping the holes would leave gaps in a header band.
|
|
119
|
-
* The cost is bounded by construction — a range is always a bounded rectangle, and whole-axis
|
|
120
|
-
* styling belongs to {@link Worksheet.getColumn}/{@link Worksheet.getRow} instead. {@link cellCount}
|
|
121
|
-
* is the exact number of cells a write will create.
|
|
122
|
-
*/
|
|
123
56
|
get style() {
|
|
124
57
|
const style = {};
|
|
125
58
|
for (const facet of CELL_STYLE_FACETS)
|
|
@@ -130,83 +63,61 @@ export class Range {
|
|
|
130
63
|
for (const cell of this.#materialise())
|
|
131
64
|
applyCellStyle(cell, style);
|
|
132
65
|
}
|
|
133
|
-
/**
|
|
134
|
-
* Strip every style facet from every cell in the block, leaving values untouched. Assigning
|
|
135
|
-
* {@link style} composes, so this is how a wholesale replace is said: `clearStyle()` then assign.
|
|
136
|
-
* Materialises nothing — a cell that does not exist carries no style to clear.
|
|
137
|
-
*/
|
|
138
66
|
clearStyle() {
|
|
139
67
|
for (const cell of this.cells) {
|
|
140
68
|
for (const facet of CELL_STYLE_FACETS)
|
|
141
69
|
setFacet(cell, facet, undefined);
|
|
142
70
|
}
|
|
143
71
|
}
|
|
144
|
-
/** Fill applied to every cell in the block; `undefined` when they do not all agree. */
|
|
145
72
|
get fill() {
|
|
146
73
|
return this.#sharedFacet('fill');
|
|
147
74
|
}
|
|
148
75
|
set fill(fill) {
|
|
149
76
|
this.#writeFacet('fill', fill);
|
|
150
77
|
}
|
|
151
|
-
/** Number format applied to every cell in the block; `undefined` when they do not all agree. */
|
|
152
78
|
get numFmt() {
|
|
153
79
|
return this.#sharedFacet('numFmt');
|
|
154
80
|
}
|
|
155
81
|
set numFmt(numFmt) {
|
|
156
82
|
this.#writeFacet('numFmt', numFmt);
|
|
157
83
|
}
|
|
158
|
-
/** Font applied to every cell in the block; `undefined` when they do not all agree. */
|
|
159
84
|
get font() {
|
|
160
85
|
return this.#sharedFacet('font');
|
|
161
86
|
}
|
|
162
87
|
set font(font) {
|
|
163
88
|
this.#writeFacet('font', font);
|
|
164
89
|
}
|
|
165
|
-
/** Border applied to every cell in the block; `undefined` when they do not all agree. */
|
|
166
90
|
get border() {
|
|
167
91
|
return this.#sharedFacet('border');
|
|
168
92
|
}
|
|
169
93
|
set border(border) {
|
|
170
94
|
this.#writeFacet('border', border);
|
|
171
95
|
}
|
|
172
|
-
/** Alignment applied to every cell in the block; `undefined` when they do not all agree. */
|
|
173
96
|
get alignment() {
|
|
174
97
|
return this.#sharedFacet('alignment');
|
|
175
98
|
}
|
|
176
99
|
set alignment(alignment) {
|
|
177
100
|
this.#writeFacet('alignment', alignment);
|
|
178
101
|
}
|
|
179
|
-
/** Protection flags applied to every cell in the block; `undefined` when they do not all agree. */
|
|
180
102
|
get protection() {
|
|
181
103
|
return this.#sharedFacet('protection');
|
|
182
104
|
}
|
|
183
105
|
set protection(protection) {
|
|
184
106
|
this.#writeFacet('protection', protection);
|
|
185
107
|
}
|
|
186
|
-
// Every cell in the block, created where it does not exist yet. An address covered by a merge
|
|
187
|
-
// resolves to that region's master, so a block overlapping a merge restyles the master rather than
|
|
188
|
-
// stranding a style on a covered cell the serializer would then have to drop.
|
|
189
108
|
#materialise() {
|
|
190
109
|
return [...this.addresses()].map((address) => this.#sheet.getCell(address));
|
|
191
110
|
}
|
|
192
|
-
// Assigning a facet replaces that facet on every cell — the block-wide reading of `cell.fill = x`.
|
|
193
|
-
// Clearing one (`undefined`) touches only the cells that exist: there is nothing to clear on a hole,
|
|
194
|
-
// and materialising the block to write nothing onto it would be pure cost.
|
|
195
111
|
#writeFacet(facet, value) {
|
|
196
112
|
const cells = value === undefined ? this.cells : this.#materialise();
|
|
197
113
|
for (const cell of cells)
|
|
198
114
|
setFacet(cell, facet, value);
|
|
199
115
|
}
|
|
200
|
-
// One facet of the block-wide style onto the record being assembled. Narrowed to a single key for
|
|
201
|
-
// the same reason {@link setFacet} is — see there.
|
|
202
116
|
#collectShared(target, facet) {
|
|
203
117
|
const shared = this.#sharedFacet(facet);
|
|
204
118
|
if (shared !== undefined)
|
|
205
119
|
target[facet] = shared;
|
|
206
120
|
}
|
|
207
|
-
// A facet's value when every position in the block carries a structurally identical one, else
|
|
208
|
-
// undefined. A hole counts as "no facet", so a partly-styled block is reported as disagreeing —
|
|
209
|
-
// which it does, since the empty positions render unstyled.
|
|
210
121
|
#sharedFacet(facet) {
|
|
211
122
|
let first;
|
|
212
123
|
let firstKey;
|
|
@@ -230,22 +141,10 @@ export class Range {
|
|
|
230
141
|
return first;
|
|
231
142
|
}
|
|
232
143
|
}
|
|
233
|
-
// Write one facet through a `Cell`'s own setter. Narrowed to a single key — rather than assigning
|
|
234
|
-
// `cell[facet]` with `facet` still a union — for the same reason `style.ts`'s `copyFacet` is: the
|
|
235
|
-
// compiler cannot correlate a union key with its value type across two object types, and a cast here
|
|
236
|
-
// would be the one place a facet could be written to the wrong slot without anything noticing.
|
|
237
144
|
function setFacet(cell, facet, value) {
|
|
238
145
|
const target = cell;
|
|
239
146
|
target[facet] = value;
|
|
240
147
|
}
|
|
241
|
-
// A canonical string for a facet value, so two structurally identical records compare equal whatever
|
|
242
|
-
// order their keys were written in. Sound here and nowhere near a general deep-equal: every facet is
|
|
243
|
-
// a plain data record of strings, numbers, booleans and nested records — no functions, no cycles, no
|
|
244
|
-
// class instances — which is exactly the shape JSON round-trips faithfully.
|
|
245
|
-
//
|
|
246
|
-
// `undefined` needs a sentinel because `JSON.stringify` answers it with `undefined` rather than a
|
|
247
|
-
// string. NUL is the one that cannot collide: a defined value stringifies to `"`, `{`, `[`, a digit,
|
|
248
|
-
// or a bare `true`/`false`/`null`, and a NUL *inside* a string comes back as a six-char escape.
|
|
249
148
|
function facetKey(value) {
|
|
250
149
|
if (value === undefined)
|
|
251
150
|
return '\u0000';
|
|
@@ -261,22 +160,11 @@ function checkBound(axis, value, max) {
|
|
|
261
160
|
throw new RangeError(`${axis} ${value} is out of bounds — the sheet ends at ${max}`);
|
|
262
161
|
}
|
|
263
162
|
}
|
|
264
|
-
/**
|
|
265
|
-
* Resolve an A1 range reference against a worksheet into a {@link Range}.
|
|
266
|
-
*
|
|
267
|
-
* A reference carrying a sheet prefix is accepted only when it names *this* sheet (case-insensitively,
|
|
268
|
-
* as sheet lookup is everywhere else): a range handed out by `sheetA` cannot be a block of `sheetB`,
|
|
269
|
-
* and quietly ignoring the prefix would let a pasted `Sheet2!B2:D5` style the wrong sheet.
|
|
270
|
-
*
|
|
271
|
-
* @throws {SyntaxError} if the reference is unparseable, names another sheet, or leaves an axis
|
|
272
|
-
* unbounded (`A:A`, `1:1`).
|
|
273
|
-
*/
|
|
274
163
|
export function rangeFrom(sheet, reference) {
|
|
275
164
|
const { top, left, bottom, right, sheetName, dimensions } = decodeRange(reference);
|
|
276
165
|
if (sheetName !== undefined && sheetName.toLowerCase() !== sheet.name.toLowerCase()) {
|
|
277
166
|
throw new SyntaxError(`"${reference}" names worksheet "${sheetName}", not "${sheet.name}" — a range belongs to the sheet it came from`);
|
|
278
167
|
}
|
|
279
|
-
// An unbounded *row* axis is what `A:A` has: it names whole columns, every row of them.
|
|
280
168
|
if (top === undefined || bottom === undefined) {
|
|
281
169
|
throw new SyntaxError(`"${reference}" spans whole columns (${dimensions}) — style them through getColumn(n), which says the same thing in one attribute instead of ${MAX_ROW} cells`);
|
|
282
170
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Cell } from './cell.ts';
|
|
2
|
+
import type { CellValue } from './value.ts';
|
|
3
|
+
import type { ColumnProperties, RowInput } from './worksheet.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Resolve a `RowInput` to the (1-based column, value) placements it names. A positional array maps
|
|
6
|
+
* each value to its column from A, skipping a hole or an explicit `undefined` so that column is left
|
|
7
|
+
* untouched; a keyed object maps each value under the column carrying the matching key.
|
|
8
|
+
*
|
|
9
|
+
* `Array.isArray`, not `instanceof Array`: a row built in another realm (a vm context, a browser
|
|
10
|
+
* iframe) is still an array but fails the identity check, and would then be walked as a keyed object —
|
|
11
|
+
* placing nothing.
|
|
12
|
+
*/
|
|
13
|
+
export declare function rowPlacements(values: RowInput, columns: ReadonlyMap<number, ColumnProperties>): Array<[number, CellValue]>;
|
|
14
|
+
/**
|
|
15
|
+
* Build the detached cell row an insert introduces: a fresh cell per placement, positioned at `number`,
|
|
16
|
+
* keyed by column. The grid-edit machinery then splices this map into place.
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildRowCells(number: number, values: RowInput, columns: ReadonlyMap<number, ColumnProperties>): Map<number, Cell>;
|
|
19
|
+
/**
|
|
20
|
+
* The 1-based index of the column carrying `key` (see {@link ColumnProperties.key}).
|
|
21
|
+
*
|
|
22
|
+
* @throws {AuthoringError} if no column declares that key.
|
|
23
|
+
*/
|
|
24
|
+
export declare function columnIndexByKey(columns: ReadonlyMap<number, ColumnProperties>, key: string): number;
|