@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.
Files changed (147) hide show
  1. package/README.md +20 -10
  2. package/dist/bytes.d.ts +24 -0
  3. package/dist/bytes.js +34 -0
  4. package/dist/core/address.d.ts +26 -4
  5. package/dist/core/address.js +21 -9
  6. package/dist/core/autofilter.d.ts +12 -1
  7. package/dist/core/autofilter.js +35 -12
  8. package/dist/core/axis-handle.d.ts +8 -0
  9. package/dist/core/axis-handle.js +14 -0
  10. package/dist/core/cell.d.ts +24 -0
  11. package/dist/core/cell.js +9 -1
  12. package/dist/core/color-resolution.d.ts +1 -1
  13. package/dist/core/color-resolution.js +7 -8
  14. package/dist/core/column.d.ts +4 -1
  15. package/dist/core/column.js +31 -35
  16. package/dist/core/conditional-formatting-overlay.d.ts +5 -0
  17. package/dist/core/conditional-formatting-overlay.js +11 -0
  18. package/dist/core/conditional-formatting.d.ts +45 -5
  19. package/dist/core/conditional-formatting.js +74 -0
  20. package/dist/core/data-validation-overlay.d.ts +11 -0
  21. package/dist/core/data-validation-overlay.js +21 -2
  22. package/dist/core/data-validation.d.ts +9 -2
  23. package/dist/core/data-validation.js +26 -0
  24. package/dist/core/grid-edits.d.ts +16 -0
  25. package/dist/core/grid-edits.js +26 -8
  26. package/dist/core/grid-shift.d.ts +19 -0
  27. package/dist/core/grid-shift.js +8 -0
  28. package/dist/core/image.d.ts +2 -0
  29. package/dist/core/image.js +2 -0
  30. package/dist/core/merge-index.d.ts +31 -0
  31. package/dist/core/merge-index.js +60 -0
  32. package/dist/core/merge.d.ts +12 -11
  33. package/dist/core/merge.js +36 -10
  34. package/dist/core/page-setup.d.ts +86 -2
  35. package/dist/core/page-setup.js +34 -1
  36. package/dist/core/pivot-table.d.ts +2 -0
  37. package/dist/core/pivot-table.js +23 -18
  38. package/dist/core/protection.d.ts +3 -0
  39. package/dist/core/protection.js +20 -14
  40. package/dist/core/row-input.d.ts +7 -0
  41. package/dist/core/row-input.js +10 -8
  42. package/dist/core/row.d.ts +4 -1
  43. package/dist/core/row.js +19 -23
  44. package/dist/core/style.d.ts +78 -7
  45. package/dist/core/style.js +90 -80
  46. package/dist/core/table.d.ts +26 -12
  47. package/dist/core/table.js +67 -33
  48. package/dist/core/theme.d.ts +3 -1
  49. package/dist/core/theme.js +4 -0
  50. package/dist/core/used-extent.d.ts +44 -0
  51. package/dist/core/used-extent.js +102 -0
  52. package/dist/core/workbook.d.ts +2 -2
  53. package/dist/core/worksheet-comments.d.ts +8 -0
  54. package/dist/core/worksheet-comments.js +18 -0
  55. package/dist/core/worksheet.d.ts +31 -7
  56. package/dist/core/worksheet.js +72 -86
  57. package/dist/customui/ribbon.js +23 -24
  58. package/dist/entries/core.d.ts +3 -3
  59. package/dist/entries/csv.d.ts +1 -1
  60. package/dist/entries/node-unavailable.d.ts +15 -0
  61. package/dist/entries/node-unavailable.js +20 -0
  62. package/dist/entries/node.d.ts +1 -0
  63. package/dist/entries/node.js +1 -0
  64. package/dist/entries/xlsx.d.ts +0 -1
  65. package/dist/entries/xlsx.js +0 -1
  66. package/dist/io/csv/read.js +1 -1
  67. package/dist/io/csv/write.d.ts +12 -2
  68. package/dist/io/csv/write.js +17 -2
  69. package/dist/io/opc/inflate.js +3 -13
  70. package/dist/io/opc/part-paths.d.ts +1 -0
  71. package/dist/io/opc/part-paths.js +15 -0
  72. package/dist/io/opc/read-opc.d.ts +22 -3
  73. package/dist/io/opc/read-opc.js +11 -39
  74. package/dist/io/opc/rels.d.ts +12 -6
  75. package/dist/io/opc/rels.js +3 -5
  76. package/dist/io/style/xf-style.d.ts +33 -4
  77. package/dist/io/style/xf-style.js +12 -1
  78. package/dist/io/xlsb/read-styles.js +6 -3
  79. package/dist/io/xlsb/read.d.ts +1 -1
  80. package/dist/io/xlsb/read.js +5 -8
  81. package/dist/io/xlsx/cell-accumulator.d.ts +2 -4
  82. package/dist/io/xlsx/cell-accumulator.js +20 -58
  83. package/dist/io/xlsx/cell-value.d.ts +1 -1
  84. package/dist/io/xlsx/cell-value.js +7 -4
  85. package/dist/io/xlsx/color-xml.d.ts +1 -1
  86. package/dist/io/xlsx/color-xml.js +8 -7
  87. package/dist/io/xlsx/comments.js +6 -3
  88. package/dist/io/xlsx/conditional-formatting.d.ts +4 -3
  89. package/dist/io/xlsx/conditional-formatting.js +81 -65
  90. package/dist/io/xlsx/data-validation.d.ts +10 -8
  91. package/dist/io/xlsx/data-validation.js +44 -41
  92. package/dist/io/xlsx/edit-vba.js +4 -11
  93. package/dist/io/xlsx/hyperlinks.d.ts +4 -2
  94. package/dist/io/xlsx/hyperlinks.js +26 -31
  95. package/dist/io/xlsx/images.js +33 -35
  96. package/dist/io/xlsx/package-plan.d.ts +9 -1
  97. package/dist/io/xlsx/package-plan.js +29 -16
  98. package/dist/io/xlsx/part-names.d.ts +41 -0
  99. package/dist/io/xlsx/part-names.js +46 -0
  100. package/dist/io/xlsx/read-pivot.js +4 -9
  101. package/dist/io/xlsx/read-rows.js +12 -13
  102. package/dist/io/xlsx/read-shared-strings.js +9 -47
  103. package/dist/io/xlsx/read-styles.d.ts +1 -1
  104. package/dist/io/xlsx/read-styles.js +65 -43
  105. package/dist/io/xlsx/read-worksheet.d.ts +10 -1
  106. package/dist/io/xlsx/read-worksheet.js +48 -63
  107. package/dist/io/xlsx/read.d.ts +3 -4
  108. package/dist/io/xlsx/read.js +124 -146
  109. package/dist/io/xlsx/rich-runs.d.ts +41 -7
  110. package/dist/io/xlsx/rich-runs.js +95 -30
  111. package/dist/io/xlsx/sheet-properties.d.ts +1 -1
  112. package/dist/io/xlsx/sheet-properties.js +23 -26
  113. package/dist/io/xlsx/styles.d.ts +3 -15
  114. package/dist/io/xlsx/styles.js +83 -105
  115. package/dist/io/xlsx/tables.js +14 -13
  116. package/dist/io/xlsx/theme-xml.js +8 -5
  117. package/dist/io/xlsx/threaded-comments.js +20 -19
  118. package/dist/io/xlsx/workbook-xml.js +43 -36
  119. package/dist/io/xlsx/worksheet-xml.d.ts +1 -1
  120. package/dist/io/xlsx/worksheet-xml.js +47 -52
  121. package/dist/io/xlsx/write-stream.d.ts +14 -1
  122. package/dist/io/xlsx/write-stream.js +40 -33
  123. package/dist/io/xlsx/write.d.ts +5 -0
  124. package/dist/io/xlsx/write.js +155 -131
  125. package/dist/sha512.d.ts +2 -0
  126. package/dist/sha512.js +146 -0
  127. package/dist/token-set.d.ts +15 -0
  128. package/dist/token-set.js +4 -0
  129. package/dist/vba/bytes.d.ts +1 -2
  130. package/dist/vba/bytes.js +1 -12
  131. package/dist/vba/cfb-format.d.ts +24 -0
  132. package/dist/vba/cfb-format.js +12 -0
  133. package/dist/vba/cfb-writer.js +17 -17
  134. package/dist/vba/cfb.js +1 -7
  135. package/dist/vba/dir-records.d.ts +53 -0
  136. package/dist/vba/dir-records.js +28 -0
  137. package/dist/vba/ms-ovba.js +32 -11
  138. package/dist/vba/project-editor.js +5 -34
  139. package/dist/vba/project.js +15 -31
  140. package/dist/vba/vba-encoding.js +1 -1
  141. package/dist/xml/xml-read.d.ts +117 -72
  142. package/dist/xml/xml-read.js +123 -169
  143. package/dist/xml/xml-scan.d.ts +100 -0
  144. package/dist/xml/xml-scan.js +208 -0
  145. package/dist/xml/xml.d.ts +44 -2
  146. package/dist/xml/xml.js +17 -5
  147. package/package.json +22 -6
@@ -1,84 +1,89 @@
1
- const NAMED_UNDERLINE_STYLES = new Set([
2
- 'none',
3
- 'single',
4
- 'double',
5
- 'singleAccounting',
6
- 'doubleAccounting',
7
- ]);
8
- export function isNamedUnderlineStyle(value) {
9
- return NAMED_UNDERLINE_STYLES.has(value);
10
- }
11
- const FILL_PATTERN_TYPES = new Set([
12
- 'none',
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)
@@ -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
- /** Writes a value into the owning worksheet's grid at a 1-based row/column, applying the column's
9
- * style (if any) to the cell: the hook a {@link Table} uses to materialise the cells of a row
10
- * appended through {@link Table.addRow}. A worksheet supplies it when it registers the table; a table
11
- * built standalone has none and cannot write cell values. */
12
- export type TableCellWriter = (row: number, col: number, value: CellValue, style?: TableColumnStyle) => void;
13
- /** Inserts one empty row into the owning worksheet's grid at a 1-based `row`, shifting that row and
14
- * everything below it down by one: the hook a {@link Table} with a totals row uses to open a slot
15
- * for an appended data row above the totals. Relocating the totals row lives in the grid, so a
16
- * standalone table has no inserter and cannot append past a totals row. */
17
- export type TableRowInserter = (row: number) => void;
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 function isTotalsRowFunction(value: string): value is TotalsRowFunction;
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, writeCell?: TableCellWriter, insertRow?: TableRowInserter);
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. */
@@ -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 TOTALS_ROW_FUNCTIONS = new Set([
28
- 'average',
29
- 'countNums',
30
- 'count',
31
- 'max',
32
- 'min',
33
- 'stdDev',
34
- 'sum',
35
- 'var',
36
- 'custom',
37
- 'none',
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
- #writeCell;
75
- #insertRow;
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.#writeCell = writeCell;
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 (this.#insertRow === undefined) {
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
- this.#insertRow(target);
127
- values.forEach((value, index) => {
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 (this.#writeCell === undefined) {
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
- values.forEach((value, index) => {
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 >= start && this.#bottom < start + count)
178
+ if (isDeletedSpan(this.#anchorRow, this.#bottom, start, count))
144
179
  return false;
145
- const shift = (v) => (v < start ? v : v >= start + count ? v + delta : start);
146
- const top = shift(this.#anchorRow);
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;
@@ -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: ThemeColorScheme;
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,
@@ -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
+ }
@@ -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?: 'visible' | 'hidden' | 'veryHidden';
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
  }