@shbernal/ts-xlsx 1.3.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) 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 +72 -3
  5. package/dist/core/address.js +50 -10
  6. package/dist/core/autofilter.d.ts +13 -2
  7. package/dist/core/autofilter.js +35 -10
  8. package/dist/core/cell.d.ts +17 -16
  9. package/dist/core/cell.js +3 -7
  10. package/dist/core/color-resolution.d.ts +8 -8
  11. package/dist/core/color-resolution.js +7 -8
  12. package/dist/core/column.d.ts +8 -9
  13. package/dist/core/column.js +2 -4
  14. package/dist/core/comment-thread.d.ts +32 -23
  15. package/dist/core/comment-thread.js +4 -4
  16. package/dist/core/conditional-formatting-overlay.d.ts +6 -1
  17. package/dist/core/conditional-formatting-overlay.js +11 -0
  18. package/dist/core/conditional-formatting.d.ts +48 -8
  19. package/dist/core/conditional-formatting.js +88 -0
  20. package/dist/core/data-validation-overlay.d.ts +12 -1
  21. package/dist/core/data-validation-overlay.js +21 -2
  22. package/dist/core/data-validation.d.ts +12 -5
  23. package/dist/core/data-validation.js +34 -0
  24. package/dist/core/date.d.ts +3 -3
  25. package/dist/core/formula.d.ts +6 -6
  26. package/dist/core/grid-edits.d.ts +16 -0
  27. package/dist/core/grid-edits.js +29 -11
  28. package/dist/core/grid-shift.d.ts +19 -0
  29. package/dist/core/grid-shift.js +8 -0
  30. package/dist/core/image.d.ts +39 -2
  31. package/dist/core/image.js +10 -0
  32. package/dist/core/internal.d.ts +9 -1
  33. package/dist/core/limits.d.ts +4 -4
  34. package/dist/core/merge.d.ts +17 -12
  35. package/dist/core/merge.js +36 -5
  36. package/dist/core/page-setup.d.ts +20 -6
  37. package/dist/core/page-setup.js +8 -1
  38. package/dist/core/pivot-table.d.ts +4 -4
  39. package/dist/core/pivot-table.js +29 -20
  40. package/dist/core/preserved.d.ts +3 -3
  41. package/dist/core/protection.d.ts +7 -4
  42. package/dist/core/protection.js +20 -14
  43. package/dist/core/range.d.ts +16 -14
  44. package/dist/core/range.js +5 -5
  45. package/dist/core/row-input.d.ts +9 -8
  46. package/dist/core/row-input.js +12 -10
  47. package/dist/core/row.d.ts +7 -8
  48. package/dist/core/row.js +2 -4
  49. package/dist/core/style.d.ts +41 -14
  50. package/dist/core/style.js +82 -67
  51. package/dist/core/table-style.d.ts +9 -9
  52. package/dist/core/table-style.js +1 -1
  53. package/dist/core/table.d.ts +53 -51
  54. package/dist/core/table.js +83 -41
  55. package/dist/core/theme.d.ts +16 -44
  56. package/dist/core/theme.js +6 -148
  57. package/dist/core/value.d.ts +7 -7
  58. package/dist/core/workbook-protection.d.ts +2 -2
  59. package/dist/core/workbook-styles.d.ts +26 -0
  60. package/dist/core/workbook-styles.js +48 -0
  61. package/dist/core/workbook-theme.d.ts +25 -0
  62. package/dist/core/workbook-theme.js +49 -0
  63. package/dist/core/workbook-vba.d.ts +17 -0
  64. package/dist/core/workbook-vba.js +79 -0
  65. package/dist/core/workbook.d.ts +133 -87
  66. package/dist/core/workbook.js +74 -133
  67. package/dist/core/worksheet-comments.d.ts +17 -0
  68. package/dist/core/worksheet-comments.js +70 -0
  69. package/dist/core/worksheet-model.d.ts +4 -4
  70. package/dist/core/worksheet-model.js +1 -0
  71. package/dist/core/worksheet-pictures.d.ts +29 -0
  72. package/dist/core/worksheet-pictures.js +42 -0
  73. package/dist/core/worksheet.d.ts +100 -79
  74. package/dist/core/worksheet.js +63 -142
  75. package/dist/customui/errors.d.ts +1 -1
  76. package/dist/customui/ribbon.d.ts +3 -3
  77. package/dist/customui/ribbon.js +24 -23
  78. package/dist/entries/core.d.ts +7 -7
  79. package/dist/entries/core.js +1 -1
  80. package/dist/entries/csv.d.ts +1 -1
  81. package/dist/entries/node-unavailable.d.ts +15 -0
  82. package/dist/entries/node-unavailable.js +20 -0
  83. package/dist/entries/node.d.ts +1 -0
  84. package/dist/entries/node.js +1 -0
  85. package/dist/entries/vba.d.ts +1 -1
  86. package/dist/entries/vba.js +1 -1
  87. package/dist/entries/xlsx.d.ts +1 -1
  88. package/dist/entries/xlsx.js +1 -1
  89. package/dist/errors.d.ts +12 -12
  90. package/dist/io/csv/read.js +2 -3
  91. package/dist/io/csv/write.d.ts +19 -4
  92. package/dist/io/csv/write.js +34 -4
  93. package/dist/io/opc/errors.d.ts +6 -6
  94. package/dist/io/opc/inflate.d.ts +1 -1
  95. package/dist/io/opc/inflate.js +2 -12
  96. package/dist/io/opc/namespaces.d.ts +1 -1
  97. package/dist/io/opc/read-opc.d.ts +19 -3
  98. package/dist/io/opc/read-opc.js +14 -8
  99. package/dist/io/opc/read-options.d.ts +2 -2
  100. package/dist/io/opc/rels.d.ts +11 -0
  101. package/dist/io/opc/rels.js +3 -2
  102. package/dist/io/opc/sniff-format.d.ts +2 -2
  103. package/dist/io/style/xf-style.d.ts +44 -7
  104. package/dist/io/style/xf-style.js +27 -0
  105. package/dist/io/xlsb/errors.d.ts +1 -1
  106. package/dist/io/xlsb/formula.d.ts +4 -4
  107. package/dist/io/xlsb/primitives.d.ts +8 -8
  108. package/dist/io/xlsb/ptg-functions.d.ts +1 -1
  109. package/dist/io/xlsb/read-styles.js +2 -20
  110. package/dist/io/xlsb/read.d.ts +2 -2
  111. package/dist/io/xlsx/cell-accumulator.d.ts +26 -9
  112. package/dist/io/xlsx/cell-accumulator.js +89 -46
  113. package/dist/io/xlsx/cell-value.d.ts +3 -3
  114. package/dist/io/xlsx/cell-value.js +9 -6
  115. package/dist/io/xlsx/color-xml.d.ts +1 -1
  116. package/dist/io/xlsx/color-xml.js +18 -23
  117. package/dist/io/xlsx/comments.d.ts +5 -5
  118. package/dist/io/xlsx/comments.js +17 -8
  119. package/dist/io/xlsx/conditional-formatting.d.ts +4 -4
  120. package/dist/io/xlsx/conditional-formatting.js +64 -58
  121. package/dist/io/xlsx/data-validation.d.ts +3 -3
  122. package/dist/io/xlsx/data-validation.js +35 -33
  123. package/dist/io/xlsx/errors.d.ts +3 -3
  124. package/dist/io/xlsx/hyperlinks.d.ts +4 -2
  125. package/dist/io/xlsx/hyperlinks.js +10 -18
  126. package/dist/io/xlsx/images.d.ts +1 -1
  127. package/dist/io/xlsx/images.js +25 -28
  128. package/dist/io/xlsx/namespaces.d.ts +2 -2
  129. package/dist/io/xlsx/package-plan.js +5 -2
  130. package/dist/io/xlsx/read-pivot.js +4 -13
  131. package/dist/io/xlsx/read-rows.d.ts +13 -13
  132. package/dist/io/xlsx/read-rows.js +19 -55
  133. package/dist/io/xlsx/read-shared-strings.js +7 -44
  134. package/dist/io/xlsx/read-styles.d.ts +3 -3
  135. package/dist/io/xlsx/read-styles.js +42 -66
  136. package/dist/io/xlsx/read-worksheet.js +73 -129
  137. package/dist/io/xlsx/read.d.ts +4 -6
  138. package/dist/io/xlsx/read.js +104 -142
  139. package/dist/io/xlsx/rich-runs.d.ts +41 -7
  140. package/dist/io/xlsx/rich-runs.js +94 -30
  141. package/dist/io/xlsx/rich-text.d.ts +1 -1
  142. package/dist/io/xlsx/shared-strings.d.ts +2 -2
  143. package/dist/io/xlsx/sheet-properties.d.ts +1 -1
  144. package/dist/io/xlsx/sheet-properties.js +14 -10
  145. package/dist/io/xlsx/styles.d.ts +9 -22
  146. package/dist/io/xlsx/styles.js +65 -89
  147. package/dist/io/xlsx/tables.d.ts +2 -2
  148. package/dist/io/xlsx/tables.js +20 -25
  149. package/dist/io/xlsx/theme-xml.d.ts +37 -0
  150. package/dist/io/xlsx/theme-xml.js +145 -0
  151. package/dist/io/xlsx/threaded-comments.d.ts +18 -27
  152. package/dist/io/xlsx/threaded-comments.js +26 -38
  153. package/dist/io/xlsx/workbook-xml.js +14 -8
  154. package/dist/io/xlsx/worksheet-xml.d.ts +2 -2
  155. package/dist/io/xlsx/worksheet-xml.js +40 -48
  156. package/dist/io/xlsx/write-stream.d.ts +23 -16
  157. package/dist/io/xlsx/write-stream.js +13 -14
  158. package/dist/io/xlsx/write.d.ts +8 -7
  159. package/dist/io/xlsx/write.js +6 -3
  160. package/dist/io/xlsx/x14-ext.d.ts +1 -1
  161. package/dist/sha512.d.ts +2 -0
  162. package/dist/sha512.js +146 -0
  163. package/dist/vba/bytes.d.ts +11 -0
  164. package/dist/vba/bytes.js +29 -0
  165. package/dist/vba/cfb-writer.d.ts +1 -1
  166. package/dist/vba/cfb.d.ts +1 -1
  167. package/dist/vba/cfb.js +15 -51
  168. package/dist/vba/codepage.d.ts +2 -2
  169. package/dist/vba/dir-records.d.ts +53 -0
  170. package/dist/vba/dir-records.js +28 -0
  171. package/dist/vba/errors.d.ts +2 -2
  172. package/dist/vba/index.d.ts +2 -2
  173. package/dist/vba/index.js +2 -2
  174. package/dist/vba/ms-ovba.d.ts +1 -1
  175. package/dist/vba/ms-ovba.js +1 -3
  176. package/dist/vba/project-editor.d.ts +9 -9
  177. package/dist/vba/project-editor.js +6 -44
  178. package/dist/vba/project.d.ts +5 -5
  179. package/dist/vba/project.js +3 -28
  180. package/dist/vba/vba-encoding.d.ts +1 -1
  181. package/dist/xml/errors.d.ts +1 -1
  182. package/dist/xml/xml-read.d.ts +98 -9
  183. package/dist/xml/xml-read.js +70 -0
  184. package/dist/xml/xml.d.ts +74 -10
  185. package/dist/xml/xml.js +39 -7
  186. package/package.json +59 -36
@@ -1,83 +1,93 @@
1
- const NAMED_UNDERLINE_STYLES = new Set([
2
- 'none',
3
- 'single',
4
- 'double',
5
- 'singleAccounting',
6
- 'doubleAccounting',
7
- ]);
1
+ const NAMED_UNDERLINE_STYLES = {
2
+ none: true,
3
+ single: true,
4
+ double: true,
5
+ singleAccounting: true,
6
+ doubleAccounting: true,
7
+ };
8
8
  export function isNamedUnderlineStyle(value) {
9
- return NAMED_UNDERLINE_STYLES.has(value);
9
+ return Object.hasOwn(NAMED_UNDERLINE_STYLES, value);
10
+ }
11
+ export function parseArgb(value) {
12
+ const hex = value.startsWith('#') ? value.slice(1) : value;
13
+ const argb = hex.length === 6 ? `FF${hex}` : hex;
14
+ return /^[0-9a-fA-F]{8}$/.test(argb) ? argb : undefined;
10
15
  }
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
- ]);
16
+ const FILL_PATTERN_TYPES = {
17
+ none: true,
18
+ solid: true,
19
+ gray125: true,
20
+ darkGray: true,
21
+ mediumGray: true,
22
+ lightGray: true,
23
+ gray0625: true,
24
+ darkHorizontal: true,
25
+ darkVertical: true,
26
+ darkDown: true,
27
+ darkUp: true,
28
+ darkGrid: true,
29
+ darkTrellis: true,
30
+ lightHorizontal: true,
31
+ lightVertical: true,
32
+ lightDown: true,
33
+ lightUp: true,
34
+ lightGrid: true,
35
+ lightTrellis: true,
36
+ };
32
37
  export function isFillPatternType(value) {
33
- return FILL_PATTERN_TYPES.has(value);
38
+ return Object.hasOwn(FILL_PATTERN_TYPES, value);
34
39
  }
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
- ]);
40
+ const BORDER_STYLES = {
41
+ thin: true,
42
+ medium: true,
43
+ thick: true,
44
+ dashed: true,
45
+ dotted: true,
46
+ double: true,
47
+ hair: true,
48
+ mediumDashed: true,
49
+ dashDot: true,
50
+ mediumDashDot: true,
51
+ dashDotDot: true,
52
+ mediumDashDotDot: true,
53
+ slantDashDot: true,
54
+ };
50
55
  export function isBorderStyle(value) {
51
- return BORDER_STYLES.has(value);
56
+ return Object.hasOwn(BORDER_STYLES, value);
52
57
  }
58
+ const FONT_VERTICAL_ALIGNMENTS = {
59
+ superscript: true,
60
+ subscript: true,
61
+ };
53
62
  export function isFontVerticalAlignment(value) {
54
- return value === 'superscript' || value === 'subscript';
63
+ return Object.hasOwn(FONT_VERTICAL_ALIGNMENTS, value);
55
64
  }
65
+ const FONT_SCHEMES = { minor: true, major: true, none: true };
56
66
  export function isFontScheme(value) {
57
- return value === 'minor' || value === 'major' || value === 'none';
67
+ return Object.hasOwn(FONT_SCHEMES, value);
58
68
  }
59
- const HORIZONTAL_ALIGNMENTS = new Set([
60
- 'general',
61
- 'left',
62
- 'center',
63
- 'right',
64
- 'fill',
65
- 'justify',
66
- 'centerContinuous',
67
- 'distributed',
68
- ]);
69
+ const HORIZONTAL_ALIGNMENTS = {
70
+ general: true,
71
+ left: true,
72
+ center: true,
73
+ right: true,
74
+ fill: true,
75
+ justify: true,
76
+ centerContinuous: true,
77
+ distributed: true,
78
+ };
69
79
  export function isHorizontalAlignment(value) {
70
- return HORIZONTAL_ALIGNMENTS.has(value);
80
+ return Object.hasOwn(HORIZONTAL_ALIGNMENTS, value);
71
81
  }
72
- const VERTICAL_ALIGNMENTS = new Set([
73
- 'top',
74
- 'center',
75
- 'bottom',
76
- 'justify',
77
- 'distributed',
78
- ]);
82
+ const VERTICAL_ALIGNMENTS = {
83
+ top: true,
84
+ center: true,
85
+ bottom: true,
86
+ justify: true,
87
+ distributed: true,
88
+ };
79
89
  export function isVerticalAlignment(value) {
80
- return VERTICAL_ALIGNMENTS.has(value);
90
+ return Object.hasOwn(VERTICAL_ALIGNMENTS, value);
81
91
  }
82
92
  const CELL_STYLE_FACET_KEYS = {
83
93
  fill: true,
@@ -92,6 +102,11 @@ export function assignStyleFacets(target, source) {
92
102
  for (const facet of CELL_STYLE_FACETS)
93
103
  copyFacet(target, source, facet);
94
104
  }
105
+ export function pickStyleFacets(source) {
106
+ const facets = {};
107
+ assignStyleFacets(facets, source);
108
+ return facets;
109
+ }
95
110
  function copyFacet(target, source, key) {
96
111
  const value = source[key];
97
112
  if (value !== undefined)
@@ -4,19 +4,19 @@ import type { DifferentialStyle } from './style.ts';
4
4
  *
5
5
  * The first thirteen apply to a **table**; the rest style a **pivot table**, which has regions a
6
6
  * table does not have (subtotal rows, page-field labels, subheadings). Both live in the same
7
- * enumeration and the same `<tableStyle>` element what decides which regions a consumer honours is
8
- * the style's own `table`/`pivot` flags, not the element names so the type carries all of them
7
+ * enumeration and the same `<tableStyle>` element. What decides which regions a consumer honours is
8
+ * the style's own `table`/`pivot` flags, not the element names, so the type carries all of them
9
9
  * rather than splitting into two enumerations that a caller would have to choose between up front.
10
10
  */
11
11
  export declare const TABLE_STYLE_ELEMENT_TYPES: readonly ['wholeTable', 'headerRow', 'totalRow', 'firstColumn', 'lastColumn', 'firstRowStripe', 'secondRowStripe', 'firstColumnStripe', 'secondColumnStripe', 'firstHeaderCell', 'lastHeaderCell', 'firstTotalCell', 'lastTotalCell', 'firstSubtotalColumn', 'secondSubtotalColumn', 'thirdSubtotalColumn', 'firstSubtotalRow', 'secondSubtotalRow', 'thirdSubtotalRow', 'blankRow', 'firstColumnSubheading', 'secondColumnSubheading', 'thirdColumnSubheading', 'firstRowSubheading', 'secondRowSubheading', 'thirdRowSubheading', 'pageFieldLabels', 'pageFieldValues'];
12
12
  /** One region of a table or pivot that a table style can format. */
13
13
  export type TableStyleElementType = (typeof TABLE_STYLE_ELEMENT_TYPES)[number];
14
- /** The four element types banded across several rows or columns the only ones {@link TableStyleElement.size} means anything on. */
14
+ /** The four element types banded across several rows or columns: the only ones {@link TableStyleElement.size} means anything on. */
15
15
  export declare const STRIPE_ELEMENT_TYPES: ReadonlySet<TableStyleElementType>;
16
16
  export declare function isTableStyleElementType(value: string): value is TableStyleElementType;
17
17
  /**
18
18
  * How one region of a table is formatted: a {@link DifferentialStyle} laid over whatever the cells
19
- * already carry, plus for a stripe how many rows or columns wide one band is.
19
+ * already carry, plus, for a stripe, how many rows or columns wide one band is.
20
20
  *
21
21
  * A `numFmt` here is carried faithfully but has no visible effect: Excel's own table-style element
22
22
  * exposes a font, an interior and borders, and nothing for a number format. See
@@ -24,7 +24,7 @@ export declare function isTableStyleElementType(value: string): value is TableSt
24
24
  */
25
25
  export interface TableStyleElement extends DifferentialStyle {
26
26
  /**
27
- * The band width, in rows or columns, for a striped element `2` makes each band two rows deep.
27
+ * The band width, in rows or columns, for a striped element: `2` makes each band two rows deep.
28
28
  * Defaults to 1.
29
29
  *
30
30
  * Meaningful **only** on the four stripe types ({@link STRIPE_ELEMENT_TYPES}); ECMA-376 says so and
@@ -41,7 +41,7 @@ export interface TableStyleElement extends DifferentialStyle {
41
41
  * Elements are applied in the order ECMA-376 fixes, not the order they are written here: whole table,
42
42
  * then the column stripes, then the row stripes, then last/first column, header row, total row, and
43
43
  * the four corner cells. So a row stripe wins over a column stripe, and both win over the whole-table
44
- * formatting worth knowing when a stripe colour appears not to take.
44
+ * formatting, which is worth knowing when a stripe colour appears not to take.
45
45
  */
46
46
  export interface TableStyle {
47
47
  /** The name a table references, and the name Excel shows in its style gallery. */
@@ -58,10 +58,10 @@ export interface TableStyle {
58
58
  *
59
59
  * Both failures here are of the same kind: Excel accepts the file and quietly does nothing with the
60
60
  * part the caller cared about. An empty name means no table can ever reference the style, and a
61
- * `size` outside a stripe is ignored neither shows up as a repair prompt or a schema error, so the
61
+ * `size` outside a stripe is ignored. Neither shows up as a repair prompt or a schema error, so the
62
62
  * only place to catch them is the call that made them.
63
63
  *
64
- * @throws {AuthoringError} if the name is empty, or a non-stripe element carries a `size`, or a `size` is not
65
- * a positive integer.
64
+ * @throws {AuthoringError} if the name is empty, or a non-stripe element carries a `size`.
65
+ * @throws {RangeError} if a `size` is not a positive integer.
66
66
  */
67
67
  export declare function checkTableStyle(style: TableStyle): void;
@@ -56,7 +56,7 @@ export function checkTableStyle(style) {
56
56
  `${[...STRIPE_ELEMENT_TYPES].join(', ')}`);
57
57
  }
58
58
  if (!Number.isInteger(size) || size < 1) {
59
- throw new AuthoringError(`Invalid table style band size ${size}: expected a positive integer`);
59
+ throw new RangeError(`Invalid table style band size ${size}: expected a positive integer`);
60
60
  }
61
61
  }
62
62
  }
@@ -1,36 +1,51 @@
1
+ import { type GridRect } from './address.ts';
1
2
  import type { CellStyle } from './style.ts';
2
3
  import type { CellValue } from './value.ts';
3
- /** A per-column cell format applied to a table's body cells the facets Excel's table-column style
4
+ /** A per-column cell format applied to a table's body cells: the facets Excel's table-column style
4
5
  * bakes into the cells rather than storing as table metadata. Every facet ({@link CellStyle}) is
5
6
  * optional; only the ones set are applied, leaving the rest of each cell's style untouched. */
6
7
  export type TableColumnStyle = Readonly<CellStyle>;
7
- /** Writes a value into the owning worksheet's grid at a 1-based row/column, applying the column's
8
- * style (if any) to the cell the hook a {@link Table} uses to materialise the cells of a row
9
- * appended through {@link Table.addRow}. A worksheet supplies it when it registers the table; a table
10
- * built standalone has none and cannot write cell values. */
11
- export type TableCellWriter = (row: number, col: number, value: CellValue, style?: TableColumnStyle) => void;
12
- /** Inserts one empty row into the owning worksheet's grid at a 1-based `row`, shifting that row and
13
- * everything below it down by one — the hook a {@link Table} with a totals row uses to open a slot
14
- * for an appended data row above the totals. Relocating the totals row lives in the grid, so a
15
- * standalone table has no inserter and cannot append past a totals row. */
16
- 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
+ }
17
32
  /**
18
33
  * A table's visual style (`<tableStyleInfo>`): the named style to apply plus the banding/highlight
19
- * toggles. Every field is a tri-state so a round-trip stays faithful a value present in the source
34
+ * toggles. Every field is a tri-state so a round-trip stays faithful: a value present in the source
20
35
  * re-emits, one the source omitted stays omitted rather than being defaulted to `"0"`. A workbook
21
36
  * whose part carries no `<tableStyleInfo>` at all leaves {@link TableOptions.style} undefined.
22
37
  */
23
38
  export interface TableStyleInfo {
24
39
  /**
25
- * Named table style to apply one of the built-in gallery (`"TableStyleMedium2"`, …) or a custom
40
+ * Named table style to apply: one of the built-in gallery (`"TableStyleMedium2"`, …) or a custom
26
41
  * one the workbook defines with {@link Workbook.addTableStyle}.
27
42
  *
28
- * **Not validated.** A name that matches nothing renders the table unstyled, silently but this
29
- * library must not be the thing that rejects it. A reader has to accept a name from a newer Excel
30
- * than the gallery list it was built with, and a writer that threw would make round-tripping such a
31
- * file impossible; there is also no diagnostics channel to warn through, so the only options were
32
- * "throw" and "accept". Accepting is the one that never makes a readable file unreadable. If a
33
- * warning channel is ever added, this is the first thing that should use it.
43
+ * **Not validated.** A name that matches nothing renders the table unstyled, silently. Even so,
44
+ * this library must not be the thing that rejects it. A reader has to accept a name from a newer
45
+ * Excel than the gallery list it was built with, and a writer that threw would make round-tripping
46
+ * such a file impossible; there is also no diagnostics channel to warn through, so the only
47
+ * options were "throw" and "accept". Accepting is the one that never makes a readable file
48
+ * unreadable. If a warning channel is ever added, this is the first thing that should use it.
34
49
  */
35
50
  readonly name?: string;
36
51
  /** Emphasise the first column. */
@@ -44,17 +59,17 @@ export interface TableStyleInfo {
44
59
  }
45
60
  /**
46
61
  * OOXML's totals-row function names (`ST_TotalsRowFunction`) to the `SUBTOTAL` first-argument code
47
- * Excel writes into a materialised totals cell. The `10x` band ignores manually hidden rows the
62
+ * Excel writes into a materialised totals cell. The `10x` band ignores manually hidden rows, the
48
63
  * behaviour Excel's totals row uses. The one inversion trap: `count` is COUNTA (103, non-empty) while
49
64
  * `countNums` is COUNT (102, numbers only). `none` (no aggregate) has no built-in code, so a column
50
- * carrying it is absent here and its totals cell is left unmaterialised Excel accepts the blank
65
+ * carrying it is absent here and its totals cell is left unmaterialised: Excel accepts the blank
51
66
  * cell. `custom` is likewise absent: its aggregate is not a `SUBTOTAL` but the arbitrary formula
52
67
  * stored in {@link TableColumn.totalsRowFormula}, which the reader/writer round-trip and the
53
68
  * materialiser writes into the cell verbatim.
54
69
  */
55
70
  export declare const TOTALS_ROW_SUBTOTAL_CODE: Readonly<Partial<Record<TotalsRowFunction, number>>>;
56
71
  /**
57
- * The values `ST_TotalsRowFunction` (ECMA-376 §18.18.86) can take a closed OOXML enumeration Excel
72
+ * The values `ST_TotalsRowFunction` (ECMA-376 §18.18.86) can take: a closed OOXML enumeration Excel
58
73
  * does not extend over time (unlike, say, a conditional-formatting rule type), so an author-side typo
59
74
  * such as `"avg"` is a compile error here rather than a silently no-op attribute at write time.
60
75
  */
@@ -63,7 +78,7 @@ export type TotalsRowFunction = 'average' | 'countNums' | 'count' | 'max' | 'min
63
78
  export declare function isTotalsRowFunction(value: string): value is TotalsRowFunction;
64
79
  /** One column of a table: a header name and its optional totals-row behaviour. */
65
80
  export interface TableColumn {
66
- /** The column's header/display name. Must be unique within the table (case-insensitively)
81
+ /** The column's header/display name. Must be unique within the table (case-insensitively):
67
82
  * Excel writes a table with colliding column names as corrupt. A collision supplied at construction
68
83
  * is disambiguated deterministically (the first keeps its name, later clashes gain a numeric
69
84
  * suffix), the same repair the reader applies to a loaded file, rather than being rejected. */
@@ -73,7 +88,7 @@ export interface TableColumn {
73
88
  /** Built-in totals-row aggregate (`"sum"`, `"average"`, `"count"`, …), or `"custom"` when the
74
89
  * column's total is the arbitrary formula in {@link totalsRowFormula} rather than a `SUBTOTAL`. */
75
90
  readonly totalsRowFunction?: TotalsRowFunction;
76
- /** The formula (no leading `=`) backing a `totalsRowFunction: "custom"` column OOXML's
91
+ /** The formula (no leading `=`) backing a `totalsRowFunction: "custom"` column. This is OOXML's
77
92
  * `<totalsRowFormula>` child. Round-tripped verbatim and written into the totals cell as the
78
93
  * cell's formula. Meaningful only alongside `totalsRowFunction: "custom"`; ignored otherwise. */
79
94
  readonly totalsRowFormula?: string;
@@ -83,13 +98,13 @@ export interface TableColumn {
83
98
  readonly style?: TableColumnStyle;
84
99
  }
85
100
  export interface TableOptions {
86
- /** Table name a valid Excel identifier, unique across the workbook. This is the name used in
101
+ /** Table name: a valid Excel identifier, unique across the workbook. This is the name used in
87
102
  * structured formula references (`Table1[Column]`). */
88
103
  name: string;
89
104
  /** Human-facing display name shown in the UI. A free-form label (spaces allowed) that need not
90
105
  * be a valid identifier. Defaults to {@link name} when omitted. */
91
106
  displayName?: string;
92
- /** A1 reference of the table's top-left cell (an anchor, e.g. `"A1"` not the full range). */
107
+ /** A1 reference of the table's top-left cell (an anchor, e.g. `"A1"`, not the full range). */
93
108
  ref: string;
94
109
  /** The table's columns, left to right. At least one is required. */
95
110
  columns: readonly TableColumn[];
@@ -99,36 +114,23 @@ export interface TableOptions {
99
114
  headerRow?: boolean;
100
115
  /** Whether the table has a totals row. Defaults to `false`. */
101
116
  totalsRow?: boolean;
102
- /** The `totalsRowShown` flag on a table *without* a totals row Excel's record of whether a
117
+ /** The `totalsRowShown` flag on a table *without* a totals row: Excel's record of whether a
103
118
  * totals row has ever been toggled on. Tri-state so a round-trip is faithful: `false` re-emits
104
119
  * `totalsRowShown="0"`, `true` re-emits `totalsRowShown="1"`, and `undefined` (the authoring
105
- * default) emits nothing a file read without the attribute must not have one injected. Ignored
120
+ * default) emits nothing: a file read without the attribute must not have one injected. Ignored
106
121
  * when {@link totalsRow} is set, since a present totals row already implies it is shown. */
107
122
  totalsRowShown?: boolean;
108
123
  /** Whether the header row carries an autoFilter. Defaults to {@link headerRow}: a header table
109
124
  * gains an autoFilter, a headerless one never can. Set `false` to keep a header table's rows
110
- * unfiltered a file read without an autoFilter must round-trip without one being injected. */
125
+ * unfiltered: a file read without an autoFilter must round-trip without one being injected. */
111
126
  autoFilter?: boolean;
112
127
  /** The table's visual style. Preserved verbatim across a round-trip; when omitted, a freshly
113
128
  * authored table is written with Excel's default (`TableStyleMedium2`, banded rows). A part read
114
129
  * with no `<tableStyleInfo>` sets this to `undefined`. See {@link TableStyleInfo}. */
115
130
  style?: TableStyleInfo;
116
131
  }
117
- /**
118
- * Return copies of `columns` with every name made unique (case-insensitively): the first occurrence
119
- * keeps its name; a later clash gains the smallest numeric suffix that resolves it (`foo`, `foo2`,
120
- * `foo3`, …). OOXML requires unique column names within a table — Excel treats a collision as
121
- * corruption — so this is applied both when a table is authored and when one is read from a file,
122
- * keeping the two paths identical rather than rejecting a name list the reader would accept.
123
- */
124
- export declare function disambiguateColumnNames(columns: readonly TableColumn[]): TableColumn[];
125
- /** The rectangle a table occupies, in 1-based coordinates. */
126
- export interface TableRegion {
127
- readonly top: number;
128
- readonly left: number;
129
- readonly bottom: number;
130
- readonly right: number;
131
- }
132
+ /** The rectangle a table occupies, as the {@link GridRect} every range-shaped thing in the library is. */
133
+ export type TableRegion = GridRect;
132
134
  export declare class Table {
133
135
  #private;
134
136
  readonly name: string;
@@ -139,9 +141,9 @@ export declare class Table {
139
141
  readonly totalsRowShown: boolean | undefined;
140
142
  readonly autoFilter: boolean;
141
143
  readonly style: TableStyleInfo | undefined;
142
- constructor(options: TableOptions, writeCell?: TableCellWriter, insertRow?: TableRowInserter);
144
+ constructor(options: TableOptions, grid?: TableGrid);
143
145
  get columnCount(): number;
144
- /** The number of data rows (excludes the header and totals rows). Always defined a table loaded
146
+ /** The number of data rows (excludes the header and totals rows). Always defined: a table loaded
145
147
  * from a file derives it from the stored range, so reading the height never throws. */
146
148
  get rowCount(): number;
147
149
  /**
@@ -150,9 +152,9 @@ export declare class Table {
150
152
  * one, so this works identically whether the table was built in memory or read from a file.
151
153
  *
152
154
  * A table carrying a totals row appends above it: the new data row lands where the totals row sat,
153
- * and the totals row (with any sheet content below) shifts down by one exactly what inserting a
155
+ * and the totals row (with any sheet content below) shifts down by one, exactly what inserting a
154
156
  * worksheet row does. That relocation lives in the grid, so a totals-row table not attached to a
155
- * worksheet throws, as does passing `values` on any detached table there is nowhere to put them.
157
+ * worksheet throws, as does passing `values` on any detached table: there is nowhere to put them.
156
158
  */
157
159
  addRow(values?: readonly CellValue[]): void;
158
160
  /**
@@ -171,7 +173,7 @@ export declare class Table {
171
173
  */
172
174
  shiftColumns(start: number, count: number, delta: number): boolean;
173
175
  /**
174
- * The options that reconstruct this table the anchor as a single-cell ref (not the derived
176
+ * The options that reconstruct this table: the anchor as a single-cell ref (not the derived
175
177
  * full range), the columns, and the data-row count with the header/totals flags. Feeding this
176
178
  * back to the constructor yields an equivalent table, so a worksheet model can carry a table
177
179
  * losslessly across an export/import round-trip.
@@ -179,11 +181,11 @@ export declare class Table {
179
181
  get options(): TableOptions;
180
182
  /** The full A1 range the table occupies: header (if any) + data rows + totals (if any). Distinct
181
183
  * from {@link TableOptions.ref} (and {@link options}'s own `ref`), which is only the single-cell
182
- * anchor a table is constructed from this is the anchor plus the columns/rows it has grown to
184
+ * anchor a table is constructed from; this is the anchor plus the columns/rows it has grown to
183
185
  * cover. */
184
186
  get range(): string;
185
187
  /**
186
- * The autoFilter range the header row plus the data rows, never the totals row or
188
+ * The autoFilter range (the header row plus the data rows, never the totals row), or
187
189
  * `undefined` when the table has no autoFilter: either it is headerless (an autoFilter has
188
190
  * nothing to anchor to and Excel treats its presence as corruption) or its {@link autoFilter}
189
191
  * flag is off (a table read without one must not gain one on round-trip).
@@ -1,5 +1,6 @@
1
1
  import { AuthoringError } from '../errors.js';
2
- import { decodeAddress, encodeAddress } from './address.js';
2
+ import { decodeCellRef, encodeAddress } from './address.js';
3
+ import { isDeletedSpan, shiftIndex } from './grid-shift.js';
3
4
  function cloneStyleInfo(style) {
4
5
  const clone = {};
5
6
  if (style.name !== undefined && style.name !== 'None')
@@ -24,23 +25,23 @@ export const TOTALS_ROW_SUBTOTAL_CODE = {
24
25
  sum: 109,
25
26
  var: 110,
26
27
  };
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
- ]);
28
+ const TOTALS_ROW_FUNCTIONS = {
29
+ average: true,
30
+ countNums: true,
31
+ count: true,
32
+ max: true,
33
+ min: true,
34
+ stdDev: true,
35
+ sum: true,
36
+ var: true,
37
+ custom: true,
38
+ none: true,
39
+ };
39
40
  export function isTotalsRowFunction(value) {
40
- return TOTALS_ROW_FUNCTIONS.has(value);
41
+ return Object.hasOwn(TOTALS_ROW_FUNCTIONS, value);
41
42
  }
42
43
  const IDENTIFIER = /^[\p{L}\\_][\p{L}\p{N}._]*$/u;
43
- export function disambiguateColumnNames(columns) {
44
+ function disambiguateColumnNames(columns) {
44
45
  const seen = new Set();
45
46
  return columns.map((column) => {
46
47
  let candidate = column.name;
@@ -52,10 +53,10 @@ export function disambiguateColumnNames(columns) {
52
53
  }
53
54
  function validateTableName(name) {
54
55
  if (name.length === 0 || name.length > 255) {
55
- throw new AuthoringError(`table name ${JSON.stringify(name)} must be between 1 and 255 characters`);
56
+ throw new RangeError(`table name ${JSON.stringify(name)} must be between 1 and 255 characters`);
56
57
  }
57
58
  if (!IDENTIFIER.test(name)) {
58
- throw new AuthoringError(`table name ${JSON.stringify(name)} is not a valid Excel identifier it must start with a letter, ` +
59
+ throw new SyntaxError(`table name ${JSON.stringify(name)} is not a valid Excel identifier: it must start with a letter, ` +
59
60
  'underscore, or backslash and contain only letters, digits, periods, and underscores');
60
61
  }
61
62
  }
@@ -71,9 +72,8 @@ export class Table {
71
72
  #anchorCol;
72
73
  #anchorRow;
73
74
  #dataRowCount;
74
- #writeCell;
75
- #insertRow;
76
- constructor(options, writeCell, insertRow) {
75
+ #grid;
76
+ constructor(options, grid) {
77
77
  validateTableName(options.name);
78
78
  if (options.columns.length === 0) {
79
79
  throw new AuthoringError(`table "${options.name}" must declare at least one column`);
@@ -81,10 +81,16 @@ export class Table {
81
81
  if (!Number.isInteger(options.rowCount) || options.rowCount < 0) {
82
82
  throw new RangeError(`table "${options.name}" has an invalid data-row count (${options.rowCount})`);
83
83
  }
84
- const { col, row } = decodeAddress(options.ref);
85
- if (col === undefined || row === undefined) {
86
- throw new SyntaxError(`table ref "${options.ref}" must anchor at a single cell (e.g. "A1")`);
84
+ let anchor;
85
+ try {
86
+ anchor = decodeCellRef(options.ref);
87
+ }
88
+ catch (cause) {
89
+ throw new SyntaxError(`table ref "${options.ref}" must anchor at a single cell (e.g. "A1")`, {
90
+ cause,
91
+ });
87
92
  }
93
+ const { col, row } = anchor;
88
94
  this.name = options.name;
89
95
  this.displayName = options.displayName ?? options.name;
90
96
  this.columns = disambiguateColumnNames(options.columns);
@@ -96,11 +102,12 @@ export class Table {
96
102
  this.#anchorCol = col;
97
103
  this.#anchorRow = row;
98
104
  this.#dataRowCount = options.rowCount;
99
- this.#writeCell = writeCell;
100
- this.#insertRow = insertRow;
105
+ this.#grid = grid;
101
106
  if (this.#rowSpan < 1) {
102
- throw new AuthoringError(`table "${this.name}" has no rows it needs a header row or at least one data row`);
107
+ throw new AuthoringError(`table "${this.name}" has no rows: it needs a header row or at least one data row`);
103
108
  }
109
+ if (grid !== undefined)
110
+ this.#materializeFrame(grid);
104
111
  }
105
112
  get columnCount() {
106
113
  return this.columns.length;
@@ -113,32 +120,67 @@ export class Table {
113
120
  throw new RangeError(`row has ${values.length} values but table "${this.name}" has ${this.columnCount} columns`);
114
121
  }
115
122
  const target = this.#anchorRow + (this.headerRow ? 1 : 0) + this.#dataRowCount;
123
+ const grid = this.#grid;
116
124
  if (this.totalsRow) {
117
- if (this.#insertRow === undefined) {
118
- throw new AuthoringError(`table "${this.name}" is not attached to a worksheet cannot relocate its totals row to append a data row`);
125
+ if (grid === undefined) {
126
+ throw new AuthoringError(`table "${this.name}" is not attached to a worksheet: cannot relocate its totals row to append a data row`);
119
127
  }
120
- this.#insertRow(target);
121
- values.forEach((value, index) => {
122
- this.#writeCell?.(target, this.#anchorCol + index, value, this.columns[index]?.style);
123
- });
128
+ grid.insertRow(target);
129
+ this.#writeRow(grid, target, values);
124
130
  return;
125
131
  }
126
132
  if (values.length > 0) {
127
- if (this.#writeCell === undefined) {
128
- throw new AuthoringError(`table "${this.name}" is not attached to a worksheet cannot write appended row values`);
133
+ if (grid === undefined) {
134
+ throw new AuthoringError(`table "${this.name}" is not attached to a worksheet: cannot write appended row values`);
129
135
  }
130
- values.forEach((value, index) => {
131
- this.#writeCell?.(target, this.#anchorCol + index, value, this.columns[index]?.style);
132
- });
136
+ this.#writeRow(grid, target, values);
133
137
  }
134
138
  this.#dataRowCount += 1;
135
139
  }
140
+ #writeRow(grid, row, values) {
141
+ values.forEach((value, index) => {
142
+ grid.writeCell(row, this.#anchorCol + index, value, this.columns[index]?.style);
143
+ });
144
+ }
145
+ #materializeFrame(grid) {
146
+ if (this.headerRow) {
147
+ const { top, left } = this.region;
148
+ this.columns.forEach((column, index) => {
149
+ const col = left + index;
150
+ if (grid.holdsValue(top, col))
151
+ return;
152
+ grid.writeCell(top, col, column.name);
153
+ });
154
+ }
155
+ if (this.totalsRow) {
156
+ const { left, bottom } = this.region;
157
+ this.columns.forEach((column, index) => {
158
+ const col = left + index;
159
+ if (grid.holdsValue(bottom, col))
160
+ return;
161
+ if (column.totalsRowLabel !== undefined) {
162
+ grid.writeCell(bottom, col, column.totalsRowLabel);
163
+ return;
164
+ }
165
+ if (column.totalsRowFunction === undefined)
166
+ return;
167
+ const code = TOTALS_ROW_SUBTOTAL_CODE[column.totalsRowFunction];
168
+ if (code !== undefined) {
169
+ grid.writeCell(bottom, col, {
170
+ formula: `SUBTOTAL(${code},${this.name}[${column.name}])`,
171
+ });
172
+ }
173
+ else if (column.totalsRowFunction === 'custom' && column.totalsRowFormula !== undefined) {
174
+ grid.writeCell(bottom, col, { formula: column.totalsRowFormula });
175
+ }
176
+ });
177
+ }
178
+ }
136
179
  shiftRows(start, count, delta) {
137
- if (this.#anchorRow >= start && this.#bottom < start + count)
180
+ if (isDeletedSpan(this.#anchorRow, this.#bottom, start, count))
138
181
  return false;
139
- const shift = (v) => (v < start ? v : v >= start + count ? v + delta : start);
140
- const top = shift(this.#anchorRow);
141
- const bottom = shift(this.#bottom);
182
+ const top = shiftIndex(this.#anchorRow, start, count, delta, 'row');
183
+ const bottom = shiftIndex(this.#bottom, start, count, delta, 'row');
142
184
  const span = bottom - top + 1;
143
185
  const fixedRows = (this.headerRow ? 1 : 0) + (this.totalsRow ? 1 : 0);
144
186
  const dataRows = span - fixedRows;