@office-kit/xlsx 0.8.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 (220) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +319 -0
  3. package/THIRD_PARTY_NOTICES.md +56 -0
  4. package/dist/cell/cell.d.ts +234 -0
  5. package/dist/cell/index.d.ts +4 -0
  6. package/dist/cell/rich-text.d.ts +37 -0
  7. package/dist/cell-D9CaNKnU.mjs +320 -0
  8. package/dist/cell-D9CaNKnU.mjs.map +1 -0
  9. package/dist/cell-style-BEDjMX1y.mjs +1579 -0
  10. package/dist/cell-style-BEDjMX1y.mjs.map +1 -0
  11. package/dist/cell.mjs +2 -0
  12. package/dist/chart/chart-xml.d.ts +16 -0
  13. package/dist/chart/chart.d.ts +735 -0
  14. package/dist/chart/cx/chartex-xml.d.ts +6 -0
  15. package/dist/chart/cx/chartex.d.ts +279 -0
  16. package/dist/chart/index.d.ts +6 -0
  17. package/dist/chart/user-shapes-xml.d.ts +4 -0
  18. package/dist/chart/user-shapes.d.ts +61 -0
  19. package/dist/chart.mjs +232 -0
  20. package/dist/chart.mjs.map +1 -0
  21. package/dist/chartsheet/chartsheet-xml.d.ts +17 -0
  22. package/dist/chartsheet/chartsheet.d.ts +121 -0
  23. package/dist/chartsheet/index.d.ts +2 -0
  24. package/dist/chartsheet-C3-tqkPy.mjs +23 -0
  25. package/dist/chartsheet-C3-tqkPy.mjs.map +1 -0
  26. package/dist/chartsheet.mjs +2 -0
  27. package/dist/colors-ovWAwnZI.mjs +67 -0
  28. package/dist/colors-ovWAwnZI.mjs.map +1 -0
  29. package/dist/compat/numbers.d.ts +14 -0
  30. package/dist/coordinate-96Ecci4d.mjs +276 -0
  31. package/dist/coordinate-96Ecci4d.mjs.map +1 -0
  32. package/dist/datetime-B2ySVlXt.mjs +71 -0
  33. package/dist/datetime-B2ySVlXt.mjs.map +1 -0
  34. package/dist/defined-names-CviWmtQg.mjs +89 -0
  35. package/dist/defined-names-CviWmtQg.mjs.map +1 -0
  36. package/dist/differential-D4dg-qtZ.mjs +37 -0
  37. package/dist/differential-D4dg-qtZ.mjs.map +1 -0
  38. package/dist/drawing/anchor.d.ts +63 -0
  39. package/dist/drawing/dml/colors.d.ts +109 -0
  40. package/dist/drawing/dml/dml-xml.d.ts +35 -0
  41. package/dist/drawing/dml/effect.d.ts +92 -0
  42. package/dist/drawing/dml/fill.d.ts +115 -0
  43. package/dist/drawing/dml/geometry.d.ts +113 -0
  44. package/dist/drawing/dml/line.d.ts +41 -0
  45. package/dist/drawing/dml/shape-properties.d.ts +33 -0
  46. package/dist/drawing/dml/text.d.ts +218 -0
  47. package/dist/drawing/drawing-xml.d.ts +5 -0
  48. package/dist/drawing/drawing.d.ts +117 -0
  49. package/dist/drawing/image.d.ts +40 -0
  50. package/dist/drawing/index.d.ts +14 -0
  51. package/dist/drawing-BxzLuryn.mjs +415 -0
  52. package/dist/drawing-BxzLuryn.mjs.map +1 -0
  53. package/dist/drawing.mjs +119 -0
  54. package/dist/drawing.mjs.map +1 -0
  55. package/dist/escape-DFTE7ZJc.mjs +51 -0
  56. package/dist/escape-DFTE7ZJc.mjs.map +1 -0
  57. package/dist/exceptions-D-CFwxgm.mjs +37 -0
  58. package/dist/exceptions-D-CFwxgm.mjs.map +1 -0
  59. package/dist/formula/tokenizer.d.ts +61 -0
  60. package/dist/formula/translate.d.ts +67 -0
  61. package/dist/inference-B3ES3KEJ.mjs +42 -0
  62. package/dist/inference-B3ES3KEJ.mjs.map +1 -0
  63. package/dist/io/browser.d.ts +41 -0
  64. package/dist/io/index.d.ts +7 -0
  65. package/dist/io/load.d.ts +46 -0
  66. package/dist/io/node-fs.d.ts +62 -0
  67. package/dist/io/node-save.d.ts +3 -0
  68. package/dist/io/node.d.ts +17 -0
  69. package/dist/io/save.d.ts +14 -0
  70. package/dist/io/sink.d.ts +54 -0
  71. package/dist/io/source.d.ts +14 -0
  72. package/dist/io.mjs +212 -0
  73. package/dist/io.mjs.map +1 -0
  74. package/dist/load-D5cbhoGx.mjs +1069 -0
  75. package/dist/load-D5cbhoGx.mjs.map +1 -0
  76. package/dist/manifest-Dps1-OpP.mjs +801 -0
  77. package/dist/manifest-Dps1-OpP.mjs.map +1 -0
  78. package/dist/node.d.ts +3 -0
  79. package/dist/node.mjs +308 -0
  80. package/dist/node.mjs.map +1 -0
  81. package/dist/packaging/core.d.ts +45 -0
  82. package/dist/packaging/custom.d.ts +62 -0
  83. package/dist/packaging/extended.d.ts +45 -0
  84. package/dist/packaging/index.d.ts +10 -0
  85. package/dist/packaging/manifest.d.ts +24 -0
  86. package/dist/packaging/relationships.d.ts +30 -0
  87. package/dist/packaging.mjs +2 -0
  88. package/dist/parser-DuLejQy1.mjs +156 -0
  89. package/dist/parser-DuLejQy1.mjs.map +1 -0
  90. package/dist/reader-D1fNW9k1.mjs +534 -0
  91. package/dist/reader-D1fNW9k1.mjs.map +1 -0
  92. package/dist/save-RohQtgEZ.mjs +745 -0
  93. package/dist/save-RohQtgEZ.mjs.map +1 -0
  94. package/dist/schema/core.d.ts +133 -0
  95. package/dist/schema/index.d.ts +3 -0
  96. package/dist/schema/serialize.d.ts +6 -0
  97. package/dist/schema.mjs +2 -0
  98. package/dist/serialize-55EnT30e.mjs +254 -0
  99. package/dist/serialize-55EnT30e.mjs.map +1 -0
  100. package/dist/serializer-BwbgHYJV.mjs +116 -0
  101. package/dist/serializer-BwbgHYJV.mjs.map +1 -0
  102. package/dist/streaming/index.d.ts +2 -0
  103. package/dist/streaming/read-only.d.ts +38 -0
  104. package/dist/streaming/write-only.d.ts +47 -0
  105. package/dist/streaming.mjs +612 -0
  106. package/dist/streaming.mjs.map +1 -0
  107. package/dist/styles/alignment.d.ts +33 -0
  108. package/dist/styles/alignment.schema.d.ts +3 -0
  109. package/dist/styles/borders.d.ts +40 -0
  110. package/dist/styles/borders.schema.d.ts +4 -0
  111. package/dist/styles/cell-style.d.ts +270 -0
  112. package/dist/styles/colors.d.ts +128 -0
  113. package/dist/styles/colors.schema.d.ts +3 -0
  114. package/dist/styles/differential.d.ts +41 -0
  115. package/dist/styles/fills.d.ts +54 -0
  116. package/dist/styles/fills.schema.d.ts +6 -0
  117. package/dist/styles/fonts.d.ts +44 -0
  118. package/dist/styles/fonts.schema.d.ts +3 -0
  119. package/dist/styles/index.d.ts +21 -0
  120. package/dist/styles/named-styles.d.ts +52 -0
  121. package/dist/styles/numbers.d.ts +39 -0
  122. package/dist/styles/numbers.schema.d.ts +3 -0
  123. package/dist/styles/protection.d.ts +9 -0
  124. package/dist/styles/protection.schema.d.ts +3 -0
  125. package/dist/styles/stylesheet-reader.d.ts +7 -0
  126. package/dist/styles/stylesheet-writer.d.ts +3 -0
  127. package/dist/styles/stylesheet.d.ts +95 -0
  128. package/dist/styles.mjs +4 -0
  129. package/dist/stylesheet-writer-C2eRmn22.mjs +8624 -0
  130. package/dist/stylesheet-writer-C2eRmn22.mjs.map +1 -0
  131. package/dist/table-DkX6UniA.mjs +113 -0
  132. package/dist/table-DkX6UniA.mjs.map +1 -0
  133. package/dist/tree-Bbs1C8Rc.mjs +192 -0
  134. package/dist/tree-Bbs1C8Rc.mjs.map +1 -0
  135. package/dist/units-rOMQqXh2.mjs +41 -0
  136. package/dist/units-rOMQqXh2.mjs.map +1 -0
  137. package/dist/user-shapes-DfmCGKB0.mjs +252 -0
  138. package/dist/user-shapes-DfmCGKB0.mjs.map +1 -0
  139. package/dist/utf8-D91g1XTG.mjs +143 -0
  140. package/dist/utf8-D91g1XTG.mjs.map +1 -0
  141. package/dist/utils/coordinate.d.ts +103 -0
  142. package/dist/utils/css.d.ts +18 -0
  143. package/dist/utils/datetime.d.ts +38 -0
  144. package/dist/utils/escape.d.ts +34 -0
  145. package/dist/utils/exceptions.d.ts +34 -0
  146. package/dist/utils/index.d.ts +11 -0
  147. package/dist/utils/inference.d.ts +24 -0
  148. package/dist/utils/stable-stringify.d.ts +7 -0
  149. package/dist/utils/units.d.ts +14 -0
  150. package/dist/utils/utf8.d.ts +1 -0
  151. package/dist/utils.mjs +39 -0
  152. package/dist/utils.mjs.map +1 -0
  153. package/dist/workbook/calc-properties.d.ts +47 -0
  154. package/dist/workbook/defined-names.d.ts +121 -0
  155. package/dist/workbook/file-recovery.d.ts +11 -0
  156. package/dist/workbook/file-sharing.d.ts +14 -0
  157. package/dist/workbook/file-version.d.ts +13 -0
  158. package/dist/workbook/function-groups.d.ts +10 -0
  159. package/dist/workbook/index.d.ts +24 -0
  160. package/dist/workbook/protection.d.ts +35 -0
  161. package/dist/workbook/shared-strings.d.ts +57 -0
  162. package/dist/workbook/smart-tags.d.ts +13 -0
  163. package/dist/workbook/views.d.ts +89 -0
  164. package/dist/workbook/workbook-properties.d.ts +57 -0
  165. package/dist/workbook/workbook.d.ts +643 -0
  166. package/dist/workbook-HGYNRBlV.mjs +636 -0
  167. package/dist/workbook-HGYNRBlV.mjs.map +1 -0
  168. package/dist/workbook.mjs +58 -0
  169. package/dist/workbook.mjs.map +1 -0
  170. package/dist/worksheet/auto-filter.d.ts +34 -0
  171. package/dist/worksheet/cell-range.d.ts +121 -0
  172. package/dist/worksheet/comments-xml.d.ts +24 -0
  173. package/dist/worksheet/comments.d.ts +13 -0
  174. package/dist/worksheet/conditional-formatting.d.ts +150 -0
  175. package/dist/worksheet/custom-sheet-views.d.ts +43 -0
  176. package/dist/worksheet/data-consolidate.d.ts +29 -0
  177. package/dist/worksheet/data-validations.d.ts +72 -0
  178. package/dist/worksheet/dimensions.d.ts +40 -0
  179. package/dist/worksheet/errors.d.ts +40 -0
  180. package/dist/worksheet/hyperlinks.d.ts +42 -0
  181. package/dist/worksheet/index.d.ts +46 -0
  182. package/dist/worksheet/ole-objects.d.ts +37 -0
  183. package/dist/worksheet/page-setup.d.ts +173 -0
  184. package/dist/worksheet/phonetic.d.ts +11 -0
  185. package/dist/worksheet/properties.d.ts +34 -0
  186. package/dist/worksheet/protected-ranges.d.ts +19 -0
  187. package/dist/worksheet/protection.d.ts +44 -0
  188. package/dist/worksheet/reader.d.ts +38 -0
  189. package/dist/worksheet/scenarios.d.ts +36 -0
  190. package/dist/worksheet/smart-tags.d.ts +23 -0
  191. package/dist/worksheet/sort-state.d.ts +28 -0
  192. package/dist/worksheet/table-xml.d.ts +5 -0
  193. package/dist/worksheet/table.d.ts +80 -0
  194. package/dist/worksheet/views.d.ts +47 -0
  195. package/dist/worksheet/web-publish.d.ts +21 -0
  196. package/dist/worksheet/worksheet.d.ts +935 -0
  197. package/dist/worksheet/writer.d.ts +72 -0
  198. package/dist/worksheet-CmCNoIgD.mjs +1726 -0
  199. package/dist/worksheet-CmCNoIgD.mjs.map +1 -0
  200. package/dist/worksheet.mjs +247 -0
  201. package/dist/worksheet.mjs.map +1 -0
  202. package/dist/writer-DspzfkNA.mjs +221 -0
  203. package/dist/writer-DspzfkNA.mjs.map +1 -0
  204. package/dist/xml/index.d.ts +10 -0
  205. package/dist/xml/iterparse.d.ts +22 -0
  206. package/dist/xml/namespaces.d.ts +91 -0
  207. package/dist/xml/parser.d.ts +7 -0
  208. package/dist/xml/serializer.d.ts +14 -0
  209. package/dist/xml/stream-writer.d.ts +39 -0
  210. package/dist/xml/tree.d.ts +37 -0
  211. package/dist/xml.mjs +140 -0
  212. package/dist/xml.mjs.map +1 -0
  213. package/dist/zip/decompression-guard.d.ts +70 -0
  214. package/dist/zip/index.d.ts +6 -0
  215. package/dist/zip/random-access-reader.d.ts +16 -0
  216. package/dist/zip/reader.d.ts +45 -0
  217. package/dist/zip/writer.d.ts +65 -0
  218. package/dist/zip/zip64-patch.d.ts +12 -0
  219. package/dist/zip.mjs +3 -0
  220. package/package.json +147 -0
@@ -0,0 +1,1726 @@
1
+ import { o as OpenXmlSchemaError } from "./exceptions-D-CFwxgm.mjs";
2
+ import { a as columnLetterFromIndex, g as tupleToCoordinate, h as rangeBoundaries, i as columnIndexFromLetter, n as MAX_ROW, r as boundariesToRangeString, s as coordinateToTuple, t as MAX_COL } from "./coordinate-96Ecci4d.mjs";
3
+ import { o as cellValueAsString, y as makeCell } from "./cell-D9CaNKnU.mjs";
4
+ //#region src/worksheet/cell-range.ts
5
+ /** Build a CellRange from explicit 1-based bounds. */
6
+ function makeCellRange(minRow, minCol, maxRow, maxCol) {
7
+ if (!Number.isInteger(minRow) || !Number.isInteger(minCol) || !Number.isInteger(maxRow) || !Number.isInteger(maxCol)) throw new OpenXmlSchemaError("CellRange bounds must be integers");
8
+ if (minRow < 1 || maxRow < 1 || minRow > 1048576 || maxRow > 1048576) throw new OpenXmlSchemaError(`CellRange row bounds must be in [1, ${MAX_ROW}]`);
9
+ if (minCol < 1 || maxCol < 1 || minCol > 16384 || maxCol > 16384) throw new OpenXmlSchemaError(`CellRange col bounds must be in [1, ${MAX_COL}]`);
10
+ return {
11
+ minRow: Math.min(minRow, maxRow),
12
+ minCol: Math.min(minCol, maxCol),
13
+ maxRow: Math.max(minRow, maxRow),
14
+ maxCol: Math.max(minCol, maxCol)
15
+ };
16
+ }
17
+ /** Parse a range expression — wraps {@link rangeBoundaries}. */
18
+ function parseRange(input) {
19
+ return rangeBoundaries(input);
20
+ }
21
+ /** Format a CellRange back into the canonical OOXML string. */
22
+ function rangeToString(r) {
23
+ return boundariesToRangeString(r);
24
+ }
25
+ /** Inclusive containment of a single (row, col) within a range. */
26
+ function rangeContainsCell(r, row, col) {
27
+ return row >= r.minRow && row <= r.maxRow && col >= r.minCol && col <= r.maxCol;
28
+ }
29
+ /**
30
+ * A1-string convenience for {@link rangeContainsCell}. Parses `cellRef` (e.g.
31
+ * `"B3"`) and `rangeRef` (e.g. `"A1:C5"`) and returns `true` iff the cell sits
32
+ * inside the range (boundary-inclusive). Throws when either input is malformed.
33
+ */
34
+ function isCellInRange(cellRef, rangeRef) {
35
+ const { col, row } = coordinateToTuple(cellRef);
36
+ return rangeContainsCell(parseRange(rangeRef), row, col);
37
+ }
38
+ /**
39
+ * A1-string convenience for {@link rangeContainsRange}. Returns `true` iff the
40
+ * `inner` range is wholly contained by `outer` (boundary-inclusive).
41
+ * Single-cell refs are accepted on either side via parseRange. Throws on
42
+ * malformed input.
43
+ */
44
+ function isRangeInRange(inner, outer) {
45
+ return rangeContainsRange(parseRange(outer), parseRange(inner));
46
+ }
47
+ /**
48
+ * Expand (or shrink) an A1 range by adding `deltaRows` to its bottom edge and
49
+ * `deltaCols` to its right edge. The top-left corner is preserved. Negative
50
+ * deltas shrink the range; the result must still have at least 1 row and 1
51
+ * column (otherwise throws).
52
+ *
53
+ * Useful for "this is the data range — also reserve room for a totals row" or
54
+ * "include one more column to the right" patterns.
55
+ */
56
+ function expandRangeStr(range, deltaRows, deltaCols) {
57
+ if (!Number.isInteger(deltaRows) || !Number.isInteger(deltaCols)) throw new OpenXmlSchemaError("expandRangeStr: deltas must be integers");
58
+ const r = parseRange(range);
59
+ return rangeToString(makeCellRange(r.minRow, r.minCol, r.maxRow + deltaRows, r.maxCol + deltaCols));
60
+ }
61
+ /** Inclusive containment of `inner` within `outer`. */
62
+ function rangeContainsRange(outer, inner) {
63
+ return inner.minRow >= outer.minRow && inner.maxRow <= outer.maxRow && inner.minCol >= outer.minCol && inner.maxCol <= outer.maxCol;
64
+ }
65
+ /**
66
+ * Shift a range by (dr, dc) integer offsets. The returned range is clamped to
67
+ * the OOXML grid; callers that want hard bounds should pass values that keep
68
+ * the result inside the spec.
69
+ */
70
+ function shiftRange(r, dr, dc) {
71
+ if (!Number.isInteger(dr) || !Number.isInteger(dc)) throw new OpenXmlSchemaError("shiftRange: dr / dc must be integers");
72
+ return makeCellRange(r.minRow + dr, r.minCol + dc, r.maxRow + dr, r.maxCol + dc);
73
+ }
74
+ /** Bounding-box union of two ranges. Always non-null. */
75
+ function unionRange(a, b) {
76
+ return {
77
+ minRow: Math.min(a.minRow, b.minRow),
78
+ minCol: Math.min(a.minCol, b.minCol),
79
+ maxRow: Math.max(a.maxRow, b.maxRow),
80
+ maxCol: Math.max(a.maxCol, b.maxCol)
81
+ };
82
+ }
83
+ /** Returns the rectangular intersection of two ranges, or `null` when disjoint. */
84
+ function intersectionRange(a, b) {
85
+ const minRow = Math.max(a.minRow, b.minRow);
86
+ const minCol = Math.max(a.minCol, b.minCol);
87
+ const maxRow = Math.min(a.maxRow, b.maxRow);
88
+ const maxCol = Math.min(a.maxCol, b.maxCol);
89
+ if (minRow > maxRow || minCol > maxCol) return null;
90
+ return {
91
+ minRow,
92
+ minCol,
93
+ maxRow,
94
+ maxCol
95
+ };
96
+ }
97
+ /** True iff two ranges share at least one cell. */
98
+ function rangesOverlap(a, b) {
99
+ return intersectionRange(a, b) !== null;
100
+ }
101
+ /** Inclusive cell count covered by a range. */
102
+ function rangeArea(r) {
103
+ return (r.maxRow - r.minRow + 1) * (r.maxCol - r.minCol + 1);
104
+ }
105
+ /** Parse an sqref string: `"A1:B2 D5 E10:F20"`. Whitespace-delimited. */
106
+ function parseMultiCellRange(input) {
107
+ const ranges = [];
108
+ for (const piece of input.split(/\s+/)) {
109
+ if (piece.length === 0) continue;
110
+ ranges.push(parseRange(piece));
111
+ }
112
+ return { ranges };
113
+ }
114
+ /** Format a MultiCellRange back into an sqref string. */
115
+ function multiCellRangeToString(m) {
116
+ return m.ranges.map(rangeToString).join(" ");
117
+ }
118
+ /** True iff any contained range covers (row, col). */
119
+ function multiCellRangeContainsCell(m, row, col) {
120
+ for (const r of m.ranges) if (rangeContainsCell(r, row, col)) return true;
121
+ return false;
122
+ }
123
+ //#endregion
124
+ //#region src/styles/colors.ts
125
+ /**
126
+ * Legacy 64-entry palette indexed colours fall back to. Verbatim from
127
+ * openpyxl/openpyxl/styles/colors.py — must not be reordered.
128
+ */
129
+ const COLOR_INDEX = Object.freeze([
130
+ "00000000",
131
+ "00FFFFFF",
132
+ "00FF0000",
133
+ "0000FF00",
134
+ "000000FF",
135
+ "00FFFF00",
136
+ "00FF00FF",
137
+ "0000FFFF",
138
+ "00000000",
139
+ "00FFFFFF",
140
+ "00FF0000",
141
+ "0000FF00",
142
+ "000000FF",
143
+ "00FFFF00",
144
+ "00FF00FF",
145
+ "0000FFFF",
146
+ "00800000",
147
+ "00008000",
148
+ "00000080",
149
+ "00808000",
150
+ "00800080",
151
+ "00008080",
152
+ "00C0C0C0",
153
+ "00808080",
154
+ "009999FF",
155
+ "00993366",
156
+ "00FFFFCC",
157
+ "00CCFFFF",
158
+ "00660066",
159
+ "00FF8080",
160
+ "000066CC",
161
+ "00CCCCFF",
162
+ "00000080",
163
+ "00FF00FF",
164
+ "00FFFF00",
165
+ "0000FFFF",
166
+ "00800080",
167
+ "00800000",
168
+ "00008080",
169
+ "000000FF",
170
+ "0000CCFF",
171
+ "00CCFFFF",
172
+ "00CCFFCC",
173
+ "00FFFF99",
174
+ "0099CCFF",
175
+ "00FF99CC",
176
+ "00CC99FF",
177
+ "00FFCC99",
178
+ "003366FF",
179
+ "0033CCCC",
180
+ "0099CC00",
181
+ "00FFCC00",
182
+ "00FF9900",
183
+ "00FF6600",
184
+ "00666699",
185
+ "00969696",
186
+ "00003366",
187
+ "00339966",
188
+ "00003300",
189
+ "00333300",
190
+ "00993300",
191
+ "00993366",
192
+ "00333399",
193
+ "00333333"
194
+ ]);
195
+ const ARGB_RE = /^([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6})$/;
196
+ /**
197
+ * Normalise an aRGB hex string. Accepts either 6 or 8 hex digits; 6-digit input
198
+ * is padded to 8 by prefixing `00` (alpha=0 = fully opaque per Excel
199
+ * convention). Returns the canonical uppercase form.
200
+ */
201
+ function normaliseRgb(value) {
202
+ if (typeof value !== "string" || !ARGB_RE.test(value)) throw new OpenXmlSchemaError(`Color rgb must be 6 or 8 hex digits; got "${value}"`);
203
+ return (value.length === 6 ? `00${value}` : value).toUpperCase();
204
+ }
205
+ /**
206
+ * Build an immutable {@link Color}. Validates ranges (indexed in [0, 65], tint
207
+ * in [-1, 1]) and normalises rgb hex.
208
+ */
209
+ function makeColor(opts = {}) {
210
+ const out = {};
211
+ if (opts.rgb !== void 0) out.rgb = normaliseRgb(opts.rgb);
212
+ if (opts.indexed !== void 0) {
213
+ if (!Number.isInteger(opts.indexed) || opts.indexed < 0 || opts.indexed > 65) throw new OpenXmlSchemaError(`Color indexed must be in [0, 65]; got ${opts.indexed}`);
214
+ out.indexed = opts.indexed;
215
+ }
216
+ if (opts.theme !== void 0) {
217
+ if (!Number.isInteger(opts.theme) || opts.theme < 0) throw new OpenXmlSchemaError(`Color theme must be a non-negative integer; got ${opts.theme}`);
218
+ out.theme = opts.theme;
219
+ }
220
+ if (opts.auto !== void 0) out.auto = opts.auto;
221
+ if (opts.tint !== void 0) {
222
+ if (!Number.isFinite(opts.tint) || opts.tint < -1 || opts.tint > 1) throw new OpenXmlSchemaError(`Color tint must be in [-1, 1]; got ${opts.tint}`);
223
+ out.tint = opts.tint;
224
+ }
225
+ return Object.freeze(out);
226
+ }
227
+ /**
228
+ * Resolve `indexed` references against {@link COLOR_INDEX}. Returns undefined
229
+ * for 64/65 (system fg/bg, not in the palette) or out-of-range.
230
+ */
231
+ function resolveIndexedColor(idx) {
232
+ return COLOR_INDEX[idx];
233
+ }
234
+ /** Shortcut for the common opaque solid colour. */
235
+ function rgbColor(hex) {
236
+ return makeColor({ rgb: hex });
237
+ }
238
+ /**
239
+ * Read a {@link Color} value-object back to a normalised ARGB hex. Resolution
240
+ * order: explicit `rgb` → `indexed` palette lookup. Returns `undefined` for
241
+ * `theme` / `auto` / empty inputs (unresolvable without a theme), so callers
242
+ * can fall back to a default.
243
+ */
244
+ function colorToHex(color) {
245
+ if (!color) return void 0;
246
+ if (color.rgb !== void 0) return normaliseRgb(color.rgb);
247
+ if (color.indexed !== void 0) {
248
+ const rgb = resolveIndexedColor(color.indexed);
249
+ if (rgb) return rgb.toUpperCase();
250
+ }
251
+ }
252
+ /**
253
+ * Compute the relative luminance of an ARGB / RGB hex string per the WCAG 2.x
254
+ * formula. Returns a value in `[0, 1]` where 0 is black and 1 is white. The
255
+ * alpha channel (if present) is ignored.
256
+ */
257
+ function luminance(hex) {
258
+ const rgb = normaliseRgb(hex);
259
+ const r = Number.parseInt(rgb.slice(2, 4), 16) / 255;
260
+ const g = Number.parseInt(rgb.slice(4, 6), 16) / 255;
261
+ const b = Number.parseInt(rgb.slice(6, 8), 16) / 255;
262
+ const lin = (c) => c <= .03928 ? c / 12.92 : ((c + .055) / 1.055) ** 2.4;
263
+ return .2126 * lin(r) + .7152 * lin(g) + .0722 * lin(b);
264
+ }
265
+ /**
266
+ * WCAG contrast ratio between two ARGB hex colors. Returns a value in `[1,
267
+ * 21]`; 1 = identical luminance, 21 = pure black on pure white. The order of
268
+ * arguments doesn't matter.
269
+ */
270
+ function contrastRatio(hexA, hexB) {
271
+ const lA = luminance(hexA);
272
+ const lB = luminance(hexB);
273
+ const [hi, lo] = lA >= lB ? [lA, lB] : [lB, lA];
274
+ return (hi + .05) / (lo + .05);
275
+ }
276
+ /**
277
+ * Pick the higher-contrast text color (`'FF000000'` black or `'FFFFFFFF'`
278
+ * white) for a background hex. Useful when applying a solid fill and wanting
279
+ * the cell text to stay readable.
280
+ */
281
+ function pickReadableTextColor(backgroundHex) {
282
+ return luminance(backgroundHex) < .179 ? "FFFFFFFF" : "FF000000";
283
+ }
284
+ const splitArgb = (hex) => {
285
+ const rgb = normaliseRgb(hex);
286
+ return {
287
+ a: Number.parseInt(rgb.slice(0, 2), 16),
288
+ r: Number.parseInt(rgb.slice(2, 4), 16),
289
+ g: Number.parseInt(rgb.slice(4, 6), 16),
290
+ b: Number.parseInt(rgb.slice(6, 8), 16)
291
+ };
292
+ };
293
+ const toHexByte = (n) => {
294
+ return Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0").toUpperCase();
295
+ };
296
+ const clampUnit = (x) => Math.max(0, Math.min(1, x));
297
+ /**
298
+ * Lighten a color by mixing it with white. `amount` is in `[0, 1]`: 0 returns
299
+ * the input unchanged, 1 returns pure white. Alpha channel is preserved.
300
+ * Equivalent to `mixColors(hex, 'FFFFFFFF', amount)`.
301
+ */
302
+ function lighten(hex, amount) {
303
+ const t = clampUnit(amount);
304
+ const { a, r, g, b } = splitArgb(hex);
305
+ return `${toHexByte(a)}${toHexByte(r + (255 - r) * t)}${toHexByte(g + (255 - g) * t)}${toHexByte(b + (255 - b) * t)}`;
306
+ }
307
+ /**
308
+ * Darken a color by mixing it with black. `amount` is in `[0, 1]`: 0 returns
309
+ * the input unchanged, 1 returns pure black (preserving the alpha channel).
310
+ */
311
+ function darken(hex, amount) {
312
+ const t = clampUnit(amount);
313
+ const { a, r, g, b } = splitArgb(hex);
314
+ return `${toHexByte(a)}${toHexByte(r * (1 - t))}${toHexByte(g * (1 - t))}${toHexByte(b * (1 - t))}`;
315
+ }
316
+ /**
317
+ * Linearly interpolate between two ARGB colors. `t = 0` returns `hexA`; `t = 1`
318
+ * returns `hexB`; intermediate values mix per channel (including alpha).
319
+ */
320
+ function mixColors(hexA, hexB, t) {
321
+ const k = clampUnit(t);
322
+ const a = splitArgb(hexA);
323
+ const b = splitArgb(hexB);
324
+ return `${toHexByte(a.a + (b.a - a.a) * k)}${toHexByte(a.r + (b.r - a.r) * k)}${toHexByte(a.g + (b.g - a.g) * k)}${toHexByte(a.b + (b.b - a.b) * k)}`;
325
+ }
326
+ /**
327
+ * Convert an ARGB / RGB hex to its HSL representation. Returns `{ h, s, l, a }`
328
+ * with `h ∈ [0, 360)`, `s ∈ [0, 1]`, `l ∈ [0, 1]`, `a ∈ [0, 255]` (alpha as the
329
+ * original byte). Useful for theme tweaking (rotate hue, desaturate, etc.)
330
+ * before round-tripping through {@link hslToHex}.
331
+ */
332
+ function hexToHsl(hex) {
333
+ const { a, r: rb, g: gb, b: bb } = splitArgb(hex);
334
+ const r = rb / 255;
335
+ const g = gb / 255;
336
+ const b = bb / 255;
337
+ const max = Math.max(r, g, b);
338
+ const min = Math.min(r, g, b);
339
+ const l = (max + min) / 2;
340
+ let h = 0;
341
+ let s = 0;
342
+ if (max !== min) {
343
+ const d = max - min;
344
+ s = l > .5 ? d / (2 - max - min) : d / (max + min);
345
+ if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) * 60;
346
+ else if (max === g) h = ((b - r) / d + 2) * 60;
347
+ else h = ((r - g) / d + 4) * 60;
348
+ }
349
+ return {
350
+ h,
351
+ s,
352
+ l,
353
+ a
354
+ };
355
+ }
356
+ /**
357
+ * Rotate the hue of a color by `degrees` (positive = clockwise). Saturation and
358
+ * lightness are preserved; alpha is preserved. Equivalent to `hexToHsl` →
359
+ * adjust `h` → `hslToHex`.
360
+ */
361
+ function rotateHue(hex, degrees) {
362
+ const { h, s, l, a } = hexToHsl(hex);
363
+ return hslToHex(h + degrees, s, l, a);
364
+ }
365
+ /**
366
+ * Adjust the saturation of a color by `delta` (added directly to the `[0, 1]`
367
+ * saturation channel and clamped). Positive = more vivid, negative = closer to
368
+ * gray. Hue, lightness, and alpha are preserved.
369
+ */
370
+ function adjustSaturation(hex, delta) {
371
+ const { h, s, l, a } = hexToHsl(hex);
372
+ return hslToHex(h, s + delta, l, a);
373
+ }
374
+ /**
375
+ * Adjust the lightness of a color by `delta` (added directly to the `[0, 1]`
376
+ * lightness channel and clamped). Positive = lighter, negative = darker.
377
+ * Distinct from {@link lighten} / {@link darken} which mix toward white/black
378
+ * in RGB space.
379
+ */
380
+ function adjustLightness(hex, delta) {
381
+ const { h, s, l, a } = hexToHsl(hex);
382
+ return hslToHex(h, s, l + delta, a);
383
+ }
384
+ /**
385
+ * Convert HSL components back to an ARGB hex string. `h` wraps mod-360, `s` and
386
+ * `l` clamp to `[0, 1]`. `alpha` is the byte (default 255 = opaque), placed in
387
+ * the high byte of the result.
388
+ */
389
+ function hslToHex(h, s, l, alpha = 255) {
390
+ const hh = (h % 360 + 360) % 360;
391
+ const ss = clampUnit(s);
392
+ const ll = clampUnit(l);
393
+ const c = (1 - Math.abs(2 * ll - 1)) * ss;
394
+ const x = c * (1 - Math.abs(hh / 60 % 2 - 1));
395
+ const m = ll - c / 2;
396
+ let rp = 0;
397
+ let gp = 0;
398
+ let bp = 0;
399
+ if (hh < 60) {
400
+ rp = c;
401
+ gp = x;
402
+ } else if (hh < 120) {
403
+ rp = x;
404
+ gp = c;
405
+ } else if (hh < 180) {
406
+ gp = c;
407
+ bp = x;
408
+ } else if (hh < 240) {
409
+ gp = x;
410
+ bp = c;
411
+ } else if (hh < 300) {
412
+ rp = x;
413
+ bp = c;
414
+ } else {
415
+ rp = c;
416
+ bp = x;
417
+ }
418
+ return `${toHexByte(Math.max(0, Math.min(255, Math.round(alpha))))}${toHexByte((rp + m) * 255)}${toHexByte((gp + m) * 255)}${toHexByte((bp + m) * 255)}`;
419
+ }
420
+ //#endregion
421
+ //#region src/worksheet/dimensions.ts
422
+ /** Build a single-column ColumnDimension entry covering `col`. */
423
+ function makeColumnDimension(col, opts = {}) {
424
+ return {
425
+ min: col,
426
+ max: col,
427
+ ...opts.width !== void 0 ? { width: opts.width } : {},
428
+ ...opts.customWidth !== void 0 ? { customWidth: opts.customWidth } : {},
429
+ ...opts.hidden !== void 0 ? { hidden: opts.hidden } : {},
430
+ ...opts.bestFit !== void 0 ? { bestFit: opts.bestFit } : {},
431
+ ...opts.outlineLevel !== void 0 ? { outlineLevel: opts.outlineLevel } : {},
432
+ ...opts.style !== void 0 ? { style: opts.style } : {},
433
+ ...opts.collapsed !== void 0 ? { collapsed: opts.collapsed } : {}
434
+ };
435
+ }
436
+ function makeRowDimension(opts = {}) {
437
+ return {
438
+ ...opts.height !== void 0 ? { height: opts.height } : {},
439
+ ...opts.customHeight !== void 0 ? { customHeight: opts.customHeight } : {},
440
+ ...opts.hidden !== void 0 ? { hidden: opts.hidden } : {},
441
+ ...opts.outlineLevel !== void 0 ? { outlineLevel: opts.outlineLevel } : {},
442
+ ...opts.collapsed !== void 0 ? { collapsed: opts.collapsed } : {},
443
+ ...opts.style !== void 0 ? { style: opts.style } : {}
444
+ };
445
+ }
446
+ //#endregion
447
+ //#region src/worksheet/hyperlinks.ts
448
+ function makeHyperlink(opts) {
449
+ if (opts.ref === void 0 || opts.ref.length === 0) throw new OpenXmlSchemaError("Hyperlink: ref is required");
450
+ return {
451
+ ref: opts.ref,
452
+ ...opts.target !== void 0 ? { target: opts.target } : {},
453
+ ...opts.location !== void 0 ? { location: opts.location } : {},
454
+ ...opts.tooltip !== void 0 ? { tooltip: opts.tooltip } : {},
455
+ ...opts.display !== void 0 ? { display: opts.display } : {},
456
+ ...opts.rId !== void 0 ? { rId: opts.rId } : {}
457
+ };
458
+ }
459
+ //#endregion
460
+ //#region src/worksheet/views.ts
461
+ /** Build a SheetView with sensible defaults. */
462
+ function makeSheetView(opts = {}) {
463
+ return {
464
+ workbookViewId: opts.workbookViewId ?? 0,
465
+ ...opts.tabSelected !== void 0 ? { tabSelected: opts.tabSelected } : {},
466
+ ...opts.showGridLines !== void 0 ? { showGridLines: opts.showGridLines } : {},
467
+ ...opts.showRowColHeaders !== void 0 ? { showRowColHeaders: opts.showRowColHeaders } : {},
468
+ ...opts.showFormulas !== void 0 ? { showFormulas: opts.showFormulas } : {},
469
+ ...opts.showZeros !== void 0 ? { showZeros: opts.showZeros } : {},
470
+ ...opts.rightToLeft !== void 0 ? { rightToLeft: opts.rightToLeft } : {},
471
+ ...opts.view !== void 0 ? { view: opts.view } : {},
472
+ ...opts.topLeftCell !== void 0 ? { topLeftCell: opts.topLeftCell } : {},
473
+ ...opts.zoomScale !== void 0 ? { zoomScale: opts.zoomScale } : {},
474
+ ...opts.zoomScaleNormal !== void 0 ? { zoomScaleNormal: opts.zoomScaleNormal } : {},
475
+ ...opts.pane ? { pane: opts.pane } : {},
476
+ ...opts.selection ? { selection: opts.selection } : {}
477
+ };
478
+ }
479
+ /**
480
+ * Build a frozen Pane from a top-left coordinate. Per Excel semantics:
481
+ * - "B2" → freeze 1 row + 1 col → xSplit=1, ySplit=1, activePane='bottomRight'
482
+ * - "A2" → freeze 1 row only → ySplit=1, activePane='bottomLeft'
483
+ * - "B1" → freeze 1 col only → xSplit=1, activePane='topRight'
484
+ * - "A1" → no freeze; throws (caller should clear `ws.views[].pane`).
485
+ */
486
+ function makeFreezePane(topLeftRef) {
487
+ const { col, row } = coordinateToTuple(topLeftRef);
488
+ if (col === 1 && row === 1) throw new OpenXmlSchemaError("makeFreezePane: \"A1\" is not a valid freeze ref (no rows or columns to freeze)");
489
+ const xSplit = col - 1;
490
+ const ySplit = row - 1;
491
+ let activePane;
492
+ if (xSplit > 0 && ySplit > 0) activePane = "bottomRight";
493
+ else if (ySplit > 0) activePane = "bottomLeft";
494
+ else activePane = "topRight";
495
+ const pane = {
496
+ state: "frozen",
497
+ topLeftCell: topLeftRef,
498
+ activePane
499
+ };
500
+ if (xSplit > 0) pane.xSplit = xSplit;
501
+ if (ySplit > 0) pane.ySplit = ySplit;
502
+ return pane;
503
+ }
504
+ /** Inverse of {@link makeFreezePane}. Returns the top-left ref of the bottomRight pane, or undefined. */
505
+ function freezePaneRef(view) {
506
+ const pane = view.pane;
507
+ if (!pane || pane.state !== "frozen") return void 0;
508
+ if (pane.topLeftCell) return pane.topLeftCell;
509
+ const xSplit = pane.xSplit ?? 0;
510
+ const ySplit = pane.ySplit ?? 0;
511
+ return tupleToCoordinate(xSplit + 1, ySplit + 1);
512
+ }
513
+ //#endregion
514
+ //#region src/worksheet/comments.ts
515
+ function makeLegacyComment(opts) {
516
+ return {
517
+ ref: opts.ref,
518
+ author: opts.author,
519
+ text: opts.text
520
+ };
521
+ }
522
+ //#endregion
523
+ //#region src/worksheet/properties.ts
524
+ const makeSheetProperties = (opts = {}) => {
525
+ const out = {};
526
+ if (opts.codeName !== void 0) out.codeName = opts.codeName;
527
+ if (opts.enableFormatConditionsCalculation !== void 0) out.enableFormatConditionsCalculation = opts.enableFormatConditionsCalculation;
528
+ if (opts.filterMode !== void 0) out.filterMode = opts.filterMode;
529
+ if (opts.published !== void 0) out.published = opts.published;
530
+ if (opts.syncHorizontal !== void 0) out.syncHorizontal = opts.syncHorizontal;
531
+ if (opts.syncRef !== void 0) out.syncRef = opts.syncRef;
532
+ if (opts.syncVertical !== void 0) out.syncVertical = opts.syncVertical;
533
+ if (opts.transitionEvaluation !== void 0) out.transitionEvaluation = opts.transitionEvaluation;
534
+ if (opts.transitionEntry !== void 0) out.transitionEntry = opts.transitionEntry;
535
+ if (opts.tabColor !== void 0) out.tabColor = opts.tabColor;
536
+ if (opts.outlinePr !== void 0) out.outlinePr = opts.outlinePr;
537
+ if (opts.pageSetUpPr !== void 0) out.pageSetUpPr = opts.pageSetUpPr;
538
+ return out;
539
+ };
540
+ //#endregion
541
+ //#region src/worksheet/worksheet.ts
542
+ /** Build a Worksheet shell. */
543
+ function makeWorksheet(title) {
544
+ if (typeof title !== "string" || title.length === 0) throw new OpenXmlSchemaError("Worksheet title must be a non-empty string");
545
+ return {
546
+ title,
547
+ rows: /* @__PURE__ */ new Map(),
548
+ _appendRowCursor: 0,
549
+ mergedCells: [],
550
+ views: [],
551
+ columnDimensions: /* @__PURE__ */ new Map(),
552
+ rowDimensions: /* @__PURE__ */ new Map(),
553
+ hyperlinks: [],
554
+ dataValidations: [],
555
+ tables: [],
556
+ legacyComments: [],
557
+ conditionalFormatting: [],
558
+ cellWatches: [],
559
+ ignoredErrors: [],
560
+ rowBreaks: [],
561
+ colBreaks: [],
562
+ customProperties: [],
563
+ webPublishItems: [],
564
+ protectedRanges: [],
565
+ smartTags: [],
566
+ customSheetViews: [],
567
+ oleObjects: [],
568
+ controls: []
569
+ };
570
+ }
571
+ const validateRowCol = (row, col) => {
572
+ if (!Number.isInteger(row) || row < 1 || row > 1048576) throw new OpenXmlSchemaError(`Worksheet row ${row} out of range [1, ${MAX_ROW}]`);
573
+ if (!Number.isInteger(col) || col < 1 || col > 16384) throw new OpenXmlSchemaError(`Worksheet col ${col} out of range [1, ${MAX_COL}]`);
574
+ };
575
+ /** Resolve a 1-based or "A1" coordinate; returns the populated Cell or undefined. */
576
+ function getCell(ws, row, col) {
577
+ return ws.rows.get(row)?.get(col);
578
+ }
579
+ /**
580
+ * Create or update a Cell at (row, col). Existing cells keep their styleId /
581
+ * hyperlinkId / commentId unless explicitly overridden.
582
+ */
583
+ function setCell(ws, row, col, value = null, styleId) {
584
+ let rowMap = ws.rows.get(row);
585
+ let cell = rowMap?.get(col);
586
+ if (cell === void 0) {
587
+ cell = makeCell(row, col, value, styleId ?? 0);
588
+ if (rowMap === void 0) {
589
+ rowMap = /* @__PURE__ */ new Map();
590
+ ws.rows.set(row, rowMap);
591
+ }
592
+ rowMap.set(col, cell);
593
+ } else {
594
+ cell.value = value;
595
+ if (styleId !== void 0) cell.styleId = styleId;
596
+ }
597
+ if (row > ws._appendRowCursor) ws._appendRowCursor = row;
598
+ return cell;
599
+ }
600
+ /** Delete a single cell from the sheet. Empty rows are pruned. */
601
+ function deleteCell(ws, row, col) {
602
+ const rowMap = ws.rows.get(row);
603
+ if (rowMap === void 0) return;
604
+ rowMap.delete(col);
605
+ if (rowMap.size === 0) ws.rows.delete(row);
606
+ }
607
+ /**
608
+ * Delete every populated cell inside a range. Returns the number of cells
609
+ * removed. Row maps that go empty are pruned. Column / row dimensions, merges,
610
+ * comments etc. are left untouched.
611
+ */
612
+ function clearRange(ws, range) {
613
+ const { minRow, maxRow, minCol, maxCol } = parseRange(range);
614
+ let n = 0;
615
+ for (let r = minRow; r <= maxRow; r++) {
616
+ const rowMap = ws.rows.get(r);
617
+ if (!rowMap) continue;
618
+ for (let c = minCol; c <= maxCol; c++) if (rowMap.delete(c)) n++;
619
+ if (rowMap.size === 0) ws.rows.delete(r);
620
+ }
621
+ return n;
622
+ }
623
+ /**
624
+ * Wipe every populated cell on the worksheet, leaving styles, dimensions,
625
+ * merges, comments, hyperlinks etc. intact. Returns the count of cells removed.
626
+ * Useful when a sheet should be re-filled from scratch but its formatting kept.
627
+ */
628
+ function clearAllCells(ws) {
629
+ let n = 0;
630
+ for (const rowMap of ws.rows.values()) n += rowMap.size;
631
+ ws.rows.clear();
632
+ ws._appendRowCursor = 0;
633
+ return n;
634
+ }
635
+ /**
636
+ * Append a row of values starting at the next empty row. Returns the row index
637
+ * (1-based). Mirrors openpyxl's `Worksheet.append`. `null` / `undefined`
638
+ * entries leave the cell empty.
639
+ */
640
+ function appendRow(ws, values) {
641
+ const row = ws._appendRowCursor + 1;
642
+ for (let i = 0; i < values.length; i++) {
643
+ const value = values[i];
644
+ if (value === void 0 || value === null) continue;
645
+ setCell(ws, row, i + 1, value);
646
+ }
647
+ ws._appendRowCursor = row;
648
+ return row;
649
+ }
650
+ /**
651
+ * Bulk version of {@link appendRow}: append a 2D array of values one row at a
652
+ * time. Returns `{firstRow, lastRow}` — both 1-based, inclusive. An empty input
653
+ * returns `{firstRow, lastRow: firstRow - 1}` so callers can detect the no-op
654
+ * without throwing.
655
+ *
656
+ * Common usage: `appendRows(ws, csvParsedRows)` for fast import.
657
+ */
658
+ function appendRows(ws, rows) {
659
+ const firstRow = ws._appendRowCursor + 1;
660
+ if (rows.length === 0) return {
661
+ firstRow,
662
+ lastRow: firstRow - 1
663
+ };
664
+ let lastRow = firstRow - 1;
665
+ for (const row of rows) lastRow = appendRow(ws, row);
666
+ return {
667
+ firstRow,
668
+ lastRow
669
+ };
670
+ }
671
+ /**
672
+ * Write a 2D array of values to the sheet starting at the given A1 anchor cell.
673
+ * Distinct from {@link appendRows} (which always writes past
674
+ * `_appendRowCursor`) — this lets you place a block at an arbitrary location,
675
+ * e.g. mid-sheet table updates.
676
+ *
677
+ * `null` / `undefined` entries leave the corresponding cell **untouched**
678
+ * (existing cell + style are preserved). Pre-existing cells inside the written
679
+ * rectangle are overwritten in place, so their `styleId` survives the write.
680
+ *
681
+ * Returns the bounding-box of the written area as 1-based inclusive
682
+ * coordinates. An empty rows array returns `undefined` rather than an invalid
683
+ * zero-area range.
684
+ */
685
+ function writeRange(ws, startRef, values) {
686
+ if (values.length === 0) return void 0;
687
+ const { col: startCol, row: startRow } = coordinateToTuple(startRef);
688
+ let maxRow = startRow;
689
+ let maxCol = startCol;
690
+ for (let i = 0; i < values.length; i++) {
691
+ const row = values[i];
692
+ if (row === void 0) continue;
693
+ const r = startRow + i;
694
+ for (let j = 0; j < row.length; j++) {
695
+ const v = row[j];
696
+ if (v === void 0 || v === null) continue;
697
+ const c = startCol + j;
698
+ setCell(ws, r, c, v);
699
+ if (c > maxCol) maxCol = c;
700
+ }
701
+ if (r > maxRow) maxRow = r;
702
+ }
703
+ return {
704
+ minRow: startRow,
705
+ maxRow,
706
+ minCol: startCol,
707
+ maxCol
708
+ };
709
+ }
710
+ /**
711
+ * Iterate the worksheet rows rectangularly. Yields one row per row in
712
+ * `[minRow, maxRow]` — including entirely empty rows — and each yielded row
713
+ * has length `maxCol - minCol + 1`. Missing cell positions are `undefined`
714
+ * (no placeholder Cell allocations).
715
+ *
716
+ * Defaults: `minRow=1`, `maxRow=getMaxRow(ws)`, `minCol=1`,
717
+ * `maxCol=getMaxCol(ws)`. The default extent is the populated bounding box,
718
+ * not the 1M × 16K sheet limit, so the rectangular default doesn't iterate
719
+ * the whole grid for a small sheet.
720
+ *
721
+ * To iterate populated rows only, filter:
722
+ * `[...iterRows(ws)].filter(row => row.some((c) => c !== undefined))`. To
723
+ * iterate populated cells without row boundaries, use {@link iterCells}.
724
+ */
725
+ function* iterRows(ws, opts = {}) {
726
+ const { minRow = 1, maxRow = getMaxRow(ws), minCol = 1, maxCol = getMaxCol(ws) } = opts;
727
+ if (maxRow < minRow || maxCol < minCol) return;
728
+ const width = maxCol - minCol + 1;
729
+ for (let r = minRow; r <= maxRow; r++) {
730
+ const rowMap = ws.rows.get(r);
731
+ const out = new Array(width).fill(void 0);
732
+ if (rowMap !== void 0) for (let c = minCol; c <= maxCol; c++) {
733
+ const cell = rowMap.get(c);
734
+ if (cell !== void 0) out[c - minCol] = cell;
735
+ }
736
+ yield out;
737
+ }
738
+ }
739
+ /**
740
+ * Same rectangular iteration as {@link iterRows}, but yields each cell's
741
+ * `.value`. Missing cell positions become `null` — already the canonical empty
742
+ * marker in `CellValue`.
743
+ */
744
+ function* iterValues(ws, opts = {}) {
745
+ for (const row of iterRows(ws, opts)) yield row.map((c) => c === void 0 ? null : c.value);
746
+ }
747
+ /**
748
+ * Yield every populated cell in the worksheet as a flat stream (row-major,
749
+ * columns ascending). Distinct from {@link iterRows} which yields one row
750
+ * per row in the bounding box — use this when the caller wants only the
751
+ * populated cells without row boundaries or rectangular padding.
752
+ */
753
+ function* iterCells(ws, opts = {}) {
754
+ for (const row of iterRows(ws, opts)) for (const cell of row) if (cell !== void 0) yield cell;
755
+ }
756
+ /** Effective max row index based on populated cells (0 when empty). */
757
+ function getMaxRow(ws) {
758
+ let m = 0;
759
+ for (const r of ws.rows.keys()) if (r > m) m = r;
760
+ return m;
761
+ }
762
+ /** Effective max column index based on populated cells (0 when empty). */
763
+ function getMaxCol(ws) {
764
+ let m = 0;
765
+ for (const rowMap of ws.rows.values()) for (const c of rowMap.keys()) if (c > m) m = c;
766
+ return m;
767
+ }
768
+ /**
769
+ * Sorted list of every row index that holds at least one populated cell.
770
+ * Returns `[]` for an empty worksheet. Useful when a caller wants to iterate
771
+ * only the rows the user actually populated, without walking 1..maxRow in dense
772
+ * fashion.
773
+ */
774
+ function getPopulatedRowIndices(ws) {
775
+ const out = [];
776
+ for (const [r, rowMap] of ws.rows) if (rowMap.size > 0) out.push(r);
777
+ return out.sort((a, b) => a - b);
778
+ }
779
+ /**
780
+ * Sorted list of every column index that holds at least one populated cell
781
+ * anywhere on the sheet. Distinct columns only; returns `[]` for an empty
782
+ * worksheet.
783
+ */
784
+ function getPopulatedColumnIndices(ws) {
785
+ const seen = /* @__PURE__ */ new Set();
786
+ for (const rowMap of ws.rows.values()) for (const c of rowMap.keys()) seen.add(c);
787
+ return [...seen].sort((a, b) => a - b);
788
+ }
789
+ /**
790
+ * Bucket a single CellValue into one of the {@link CellsByKindCounts} keys.
791
+ * Shared between {@link countCellsByKind} and the workbook-wide overview so
792
+ * the two never drift in how they classify (e.g. `Date` vs `duration`).
793
+ */
794
+ function classifyCellValue(v) {
795
+ if (v === null) return "null";
796
+ if (typeof v === "string") return "string";
797
+ if (typeof v === "number") return "number";
798
+ if (typeof v === "boolean") return "boolean";
799
+ if (v instanceof Date) return "date";
800
+ const kind = v.kind;
801
+ if (kind === "duration") return "duration";
802
+ if (kind === "error") return "error";
803
+ if (kind === "rich-text") return "rich-text";
804
+ return "formula";
805
+ }
806
+ function countCellsByKind(ws) {
807
+ const out = {
808
+ null: 0,
809
+ string: 0,
810
+ number: 0,
811
+ boolean: 0,
812
+ date: 0,
813
+ duration: 0,
814
+ error: 0,
815
+ "rich-text": 0,
816
+ formula: 0
817
+ };
818
+ for (const cell of iterCells(ws)) out[classifyCellValue(cell.value)]++;
819
+ return out;
820
+ }
821
+ /**
822
+ * True iff the worksheet has zero non-empty cells. Equivalent to
823
+ * `getNonEmptyCellCount(ws) === 0` but short-circuits on the first non-null
824
+ * value found, so the cost is O(first non-empty cell) rather than O(populated
825
+ * cells).
826
+ */
827
+ function isWorksheetEmpty(ws) {
828
+ for (const cell of iterCells(ws)) if (cell.value !== null) return false;
829
+ return true;
830
+ }
831
+ /**
832
+ * Count non-empty cells. Distinct from {@link countCells} which counts every
833
+ * materialised cell (including ones whose `value === null`): this skips cells
834
+ * with a `null` value, plus optionally formulas / rich-text per the opts.
835
+ *
836
+ * Useful for "how many real values does this sheet contain" stats vs the
837
+ * materialised footprint.
838
+ */
839
+ function getNonEmptyCellCount(ws, opts = {}) {
840
+ const includeFormulas = opts.includeFormulas ?? true;
841
+ const includeRichText = opts.includeRichText ?? true;
842
+ let n = 0;
843
+ for (const cell of iterCells(ws)) {
844
+ const v = cell.value;
845
+ if (v === null) continue;
846
+ if (typeof v === "object" && v !== null) {
847
+ const kind = v.kind;
848
+ if (kind === "formula" && !includeFormulas) continue;
849
+ if (kind === "rich-text" && !includeRichText) continue;
850
+ }
851
+ n++;
852
+ }
853
+ return n;
854
+ }
855
+ /** Total populated cell count. */
856
+ function countCells(ws) {
857
+ let n = 0;
858
+ for (const rowMap of ws.rows.values()) n += rowMap.size;
859
+ return n;
860
+ }
861
+ /**
862
+ * Bounding-box of the populated cells: `{ minRow, maxRow, minCol, maxCol }`
863
+ * covering every cell in `ws.rows`. Returns `undefined` when the sheet is
864
+ * empty. Walks the sparse store once.
865
+ */
866
+ function getDataExtent(ws) {
867
+ let minRow = Number.POSITIVE_INFINITY;
868
+ let maxRow = 0;
869
+ let minCol = Number.POSITIVE_INFINITY;
870
+ let maxCol = 0;
871
+ let touched = false;
872
+ for (const [r, rowMap] of ws.rows) {
873
+ if (rowMap.size === 0) continue;
874
+ if (r < minRow) minRow = r;
875
+ if (r > maxRow) maxRow = r;
876
+ for (const c of rowMap.keys()) {
877
+ if (c < minCol) minCol = c;
878
+ if (c > maxCol) maxCol = c;
879
+ }
880
+ touched = true;
881
+ }
882
+ if (!touched) return void 0;
883
+ return {
884
+ minRow,
885
+ maxRow,
886
+ minCol,
887
+ maxCol
888
+ };
889
+ }
890
+ /**
891
+ * Iterate every populated cell, yielding those for which `predicate` returns
892
+ * true. Iteration order is row-then-column ascending. Cells whose `.value ===
893
+ * null` (empty placeholders carrying only style or comment metadata) are still
894
+ * visited.
895
+ */
896
+ function* findCells(ws, predicate) {
897
+ const rowKeys = [...ws.rows.keys()].sort((a, b) => a - b);
898
+ for (const r of rowKeys) {
899
+ const rowMap = ws.rows.get(r);
900
+ if (!rowMap) continue;
901
+ const cols = [...rowMap.keys()].sort((a, b) => a - b);
902
+ for (const c of cols) {
903
+ const cell = rowMap.get(c);
904
+ if (cell !== void 0 && predicate(cell)) yield cell;
905
+ }
906
+ }
907
+ }
908
+ /**
909
+ * Iterate the populated cells inside a rectangular range. Cells that don't
910
+ * exist in the sparse store are skipped (no auto-allocate). Use {@link
911
+ * applyToRange} when you need every coordinate visited regardless of
912
+ * population.
913
+ */
914
+ function* getCellsInRange(ws, range) {
915
+ const { minRow, maxRow, minCol, maxCol } = parseRange(range);
916
+ for (let r = minRow; r <= maxRow; r++) {
917
+ const rowMap = ws.rows.get(r);
918
+ if (!rowMap) continue;
919
+ for (let col = minCol; col <= maxCol; col++) {
920
+ const cell = rowMap.get(col);
921
+ if (cell !== void 0) yield cell;
922
+ }
923
+ }
924
+ }
925
+ /** Resolve an "A1" coordinate to a numeric (col, row) pair on the sheet. */
926
+ function setCellByCoord(ws, coord, value, styleId) {
927
+ const m = /^([A-Za-z]{1,3})([1-9][0-9]*)$/.exec(coord);
928
+ if (m === null || m[1] === void 0 || m[2] === void 0) throw new OpenXmlSchemaError(`setCellByCoord: invalid coordinate "${coord}"`);
929
+ const col = columnIndexFromLetter(m[1]);
930
+ return setCell(ws, Number.parseInt(m[2], 10), col, value, styleId);
931
+ }
932
+ /** Convenience getter accepting an "A1" coordinate. */
933
+ function getCellByCoord(ws, coord) {
934
+ const m = /^([A-Za-z]{1,3})([1-9][0-9]*)$/.exec(coord);
935
+ if (m === null || m[1] === void 0 || m[2] === void 0) return void 0;
936
+ const col = columnIndexFromLetter(m[1]);
937
+ return getCell(ws, Number.parseInt(m[2], 10), col);
938
+ }
939
+ const toCellRange = (refOrRange) => typeof refOrRange === "string" ? parseRange(refOrRange) : refOrRange;
940
+ /**
941
+ * Merge a range. The top-left cell keeps its value; every other cell in the
942
+ * range is dropped from `ws.rows` so the on-wire `<sheetData>` won't carry
943
+ * phantom cells underneath the merge. Mirrors openpyxl's
944
+ * `MergedCellRange.format()`. Idempotent for an identical range, throws when
945
+ * the range overlaps an existing merge.
946
+ */
947
+ function mergeCells(ws, refOrRange) {
948
+ const range = toCellRange(refOrRange);
949
+ for (const existing of ws.mergedCells) {
950
+ if (rangeToString(existing) === rangeToString(range)) return existing;
951
+ if (rangesOverlap(existing, range)) throw new OpenXmlSchemaError(`mergeCells: range ${rangeToString(range)} overlaps existing merged range ${rangeToString(existing)}`);
952
+ }
953
+ for (let r = range.minRow; r <= range.maxRow; r++) for (let c = range.minCol; c <= range.maxCol; c++) {
954
+ if (r === range.minRow && c === range.minCol) continue;
955
+ ws.rows.get(r)?.delete(c);
956
+ const row = ws.rows.get(r);
957
+ if (row && row.size === 0) ws.rows.delete(r);
958
+ }
959
+ ws.mergedCells.push(range);
960
+ return range;
961
+ }
962
+ /** Drop a previously-merged range. No-op if the range isn't registered. */
963
+ function unmergeCells(ws, refOrRange) {
964
+ const target = rangeToString(toCellRange(refOrRange));
965
+ const idx = ws.mergedCells.findIndex((r) => rangeToString(r) === target);
966
+ if (idx < 0) return false;
967
+ ws.mergedCells.splice(idx, 1);
968
+ return true;
969
+ }
970
+ /** Read-only iterator over the worksheet's merged ranges. */
971
+ function getMergedCells(ws) {
972
+ return ws.mergedCells;
973
+ }
974
+ /** True iff (row, col) sits inside any merged range — top-left included. */
975
+ function isMergedCell(ws, row, col) {
976
+ for (const range of ws.mergedCells) if (rangeContainsCell(range, row, col)) return true;
977
+ return false;
978
+ }
979
+ /**
980
+ * Look up the merged range covering (row, col), or `undefined` if the
981
+ * coordinate isn't inside any merge. Lets callers introspect a merge without
982
+ * iterating `getMergedCells` themselves.
983
+ */
984
+ function getMergedRangeAt(ws, row, col) {
985
+ for (const range of ws.mergedCells) if (rangeContainsCell(range, row, col)) return range;
986
+ }
987
+ /**
988
+ * Drop the merge that contains (row, col), if any. Returns `true` when a merge
989
+ * was unregistered. Useful when callers know a cell coordinate but not the
990
+ * original merge bounds.
991
+ */
992
+ function unmergeCellsAt(ws, row, col) {
993
+ for (let i = 0; i < ws.mergedCells.length; i++) {
994
+ const r = ws.mergedCells[i];
995
+ if (r && rangeContainsCell(r, row, col)) {
996
+ ws.mergedCells.splice(i, 1);
997
+ return true;
998
+ }
999
+ }
1000
+ return false;
1001
+ }
1002
+ /**
1003
+ * Drop every merged range on the worksheet. Returns the count of merges
1004
+ * removed. Cells that were inside the merges keep their values — only the merge
1005
+ * metadata is gone.
1006
+ */
1007
+ function removeAllMergedRanges(ws) {
1008
+ const n = ws.mergedCells.length;
1009
+ ws.mergedCells = [];
1010
+ return n;
1011
+ }
1012
+ /** Lazily get-or-create the primary SheetView so view-mutating helpers don't have to branch. */
1013
+ const ensurePrimaryView = (ws) => {
1014
+ let view = ws.views[0];
1015
+ if (!view) {
1016
+ view = makeSheetView();
1017
+ ws.views.push(view);
1018
+ }
1019
+ return view;
1020
+ };
1021
+ /**
1022
+ * Freeze rows / columns above + left of `topLeftRef` ("B2" → 1 row + 1 col).
1023
+ * Pass `undefined` to clear any existing freeze. Targets the workbook's primary
1024
+ * SheetView (`ws.views[0]`); creates one if absent.
1025
+ */
1026
+ function setFreezePanes(ws, topLeftRef) {
1027
+ if (topLeftRef === void 0) {
1028
+ if (ws.views[0]) delete ws.views[0].pane;
1029
+ return;
1030
+ }
1031
+ const view = ensurePrimaryView(ws);
1032
+ view.pane = makeFreezePane(topLeftRef);
1033
+ }
1034
+ /** Inverse of {@link setFreezePanes}; returns the top-left ref or undefined when no freeze is active. */
1035
+ function getFreezePanes(ws) {
1036
+ const view = ws.views[0];
1037
+ if (!view) return void 0;
1038
+ return freezePaneRef(view);
1039
+ }
1040
+ /** Freeze both top `rows` rows AND left `cols` columns. */
1041
+ function freezePanes(ws, rows, cols) {
1042
+ if (!Number.isInteger(rows) || rows < 1) throw new OpenXmlSchemaError(`freezePanes: rows must be a positive integer; got ${rows}`);
1043
+ if (!Number.isInteger(cols) || cols < 1) throw new OpenXmlSchemaError(`freezePanes: cols must be a positive integer; got ${cols}`);
1044
+ setFreezePanes(ws, `${columnLetterFromIndex(cols + 1)}${rows + 1}`);
1045
+ }
1046
+ /** Lazily get-or-create `ws.sheetProperties` so tab-color helpers don't have to branch. */
1047
+ const ensureSheetProperties = (ws) => {
1048
+ if (!ws.sheetProperties) ws.sheetProperties = makeSheetProperties();
1049
+ return ws.sheetProperties;
1050
+ };
1051
+ const colorFrom = (input) => typeof input === "string" ? makeColor({ rgb: input }) : makeColor(input);
1052
+ /**
1053
+ * Set the sheet tab strip colour. Accepts either a hex string (`"FF0070C0"`) or
1054
+ * a partial `Color` object (`{ theme: 4, tint: 0.4 }`).
1055
+ */
1056
+ function setSheetTabColor(ws, color) {
1057
+ const c = colorFrom(color);
1058
+ ensureSheetProperties(ws).tabColor = c;
1059
+ return c;
1060
+ }
1061
+ /**
1062
+ * Set the zoom scale (percent) on the primary SheetView. Excel accepts integer
1063
+ * percentages in `[10, 400]`.
1064
+ */
1065
+ function setSheetZoom(ws, scale) {
1066
+ if (!Number.isInteger(scale) || scale < 10 || scale > 400) throw new OpenXmlSchemaError(`setSheetZoom: scale must be an integer in [10, 400]; got ${scale}`);
1067
+ ensurePrimaryView(ws).zoomScale = scale;
1068
+ }
1069
+ /** Switch the sheet view between Excel's "Normal" / "Page Break Preview" / "Page Layout" modes. */
1070
+ function setSheetViewMode(ws, mode) {
1071
+ ensurePrimaryView(ws).view = mode;
1072
+ }
1073
+ /**
1074
+ * Set values across a rectangular range from a 2-D array. `rows[0]` is laid
1075
+ * down starting at the top-left of `range`; subsequent rows follow. `null` /
1076
+ * `undefined` entries skip the cell. Useful for dropping a header + data block
1077
+ * in one call.
1078
+ */
1079
+ function setRangeValues(ws, range, rows) {
1080
+ const { minRow, minCol } = parseRange(range);
1081
+ for (let i = 0; i < rows.length; i++) {
1082
+ const row = rows[i];
1083
+ if (!row) continue;
1084
+ for (let j = 0; j < row.length; j++) {
1085
+ const v = row[j];
1086
+ if (v === null || v === void 0) continue;
1087
+ setCell(ws, minRow + i, minCol + j, v);
1088
+ }
1089
+ }
1090
+ }
1091
+ /**
1092
+ * Iterate over every cell coordinate in a range, calling `visit` once per (row,
1093
+ * col). Allocates the cell on first touch so callers can mutate it freely.
1094
+ */
1095
+ function applyToRange(ws, range, visit) {
1096
+ const { minRow, maxRow, minCol, maxCol } = parseRange(range);
1097
+ for (let r = minRow; r <= maxRow; r++) for (let c = minCol; c <= maxCol; c++) {
1098
+ let cell = ws.rows.get(r)?.get(c);
1099
+ if (!cell) cell = setCell(ws, r, c);
1100
+ visit(cell, r, c);
1101
+ }
1102
+ }
1103
+ /**
1104
+ * Read a rectangular range as a dense 2-D array of values. Empty cells yield
1105
+ * `null`. The shape is `[maxRow - minRow + 1] × [maxCol - minCol + 1]`. Inverse
1106
+ * of {@link setRangeValues}.
1107
+ */
1108
+ function getRangeValues(ws, range) {
1109
+ const { minRow, maxRow, minCol, maxCol } = parseRange(range);
1110
+ const rowsOut = [];
1111
+ for (let r = minRow; r <= maxRow; r++) {
1112
+ const rowMap = ws.rows.get(r);
1113
+ const row = [];
1114
+ for (let c = minCol; c <= maxCol; c++) {
1115
+ const cell = rowMap?.get(c);
1116
+ row.push(cell ? cell.value : null);
1117
+ }
1118
+ rowsOut.push(row);
1119
+ }
1120
+ return rowsOut;
1121
+ }
1122
+ /**
1123
+ * Copy every populated cell from `source` to `target` (within the same
1124
+ * worksheet, or across worksheets via `targetWs`). Cells are shallow-cloned:
1125
+ * `value` and `styleId` carry over but `row`/`col` are rewritten. The target's
1126
+ * existing cells in the destination extent are overwritten; cells outside are
1127
+ * untouched.
1128
+ *
1129
+ * The source and target ranges define the top-left corner — their dimensions
1130
+ * need not match. If the target range is smaller than the source, only the
1131
+ * cells that fit within the target's extent are copied; if larger, only the
1132
+ * source's extent is filled.
1133
+ *
1134
+ * Returns the number of cells copied.
1135
+ */
1136
+ function copyRange(ws, source, target, opts = {}) {
1137
+ const dest = opts.targetWs ?? ws;
1138
+ const sameSheet = dest === ws;
1139
+ const src = parseRange(source);
1140
+ const dst = parseRange(target);
1141
+ const dr = dst.minRow - src.minRow;
1142
+ const dc = dst.minCol - src.minCol;
1143
+ const maxRowOffset = Math.min(src.maxRow - src.minRow, dst.maxRow - dst.minRow);
1144
+ const maxColOffset = Math.min(src.maxCol - src.minCol, dst.maxCol - dst.minCol);
1145
+ let n = 0;
1146
+ for (let i = 0; i <= maxRowOffset; i++) {
1147
+ const srcRow = ws.rows.get(src.minRow + i);
1148
+ if (!srcRow) continue;
1149
+ for (let j = 0; j <= maxColOffset; j++) {
1150
+ const srcCell = srcRow.get(src.minCol + j);
1151
+ if (!srcCell) continue;
1152
+ const newCell = setCell(dest, src.minRow + i + dr, src.minCol + j + dc, srcCell.value, srcCell.styleId);
1153
+ if (sameSheet) {
1154
+ if (srcCell.hyperlinkId !== void 0) newCell.hyperlinkId = srcCell.hyperlinkId;
1155
+ if (srcCell.commentId !== void 0) newCell.commentId = srcCell.commentId;
1156
+ }
1157
+ n++;
1158
+ }
1159
+ }
1160
+ return n;
1161
+ }
1162
+ /**
1163
+ * Move every populated cell from `source` to `target`. Equivalent to
1164
+ * `copyRange` followed by clearing the source. When the ranges overlap on the
1165
+ * same sheet, the copy walks in the direction that preserves data — high-to-low
1166
+ * along any axis where the move shifts forward, low-to-high otherwise — so
1167
+ * cells aren't overwritten before they've been read. Returns the number of
1168
+ * cells moved.
1169
+ */
1170
+ function moveRange(ws, source, target, opts = {}) {
1171
+ const dest = opts.targetWs ?? ws;
1172
+ const sameSheet = dest === ws;
1173
+ const src = parseRange(source);
1174
+ const dst = parseRange(target);
1175
+ const dr = dst.minRow - src.minRow;
1176
+ const dc = dst.minCol - src.minCol;
1177
+ const maxRowOffset = Math.min(src.maxRow - src.minRow, dst.maxRow - dst.minRow);
1178
+ const maxColOffset = Math.min(src.maxCol - src.minCol, dst.maxCol - dst.minCol);
1179
+ const snap = [];
1180
+ for (let i = 0; i <= maxRowOffset; i++) {
1181
+ const srcRow = ws.rows.get(src.minRow + i);
1182
+ if (!srcRow) continue;
1183
+ for (let j = 0; j <= maxColOffset; j++) {
1184
+ const srcCell = srcRow.get(src.minCol + j);
1185
+ if (!srcCell) continue;
1186
+ snap.push({
1187
+ row: src.minRow + i,
1188
+ col: src.minCol + j,
1189
+ cell: srcCell
1190
+ });
1191
+ }
1192
+ }
1193
+ for (let i = 0; i <= maxRowOffset; i++) {
1194
+ const rowIdx = src.minRow + i;
1195
+ const srcRow = ws.rows.get(rowIdx);
1196
+ if (!srcRow) continue;
1197
+ for (let j = 0; j <= maxColOffset; j++) srcRow.delete(src.minCol + j);
1198
+ if (srcRow.size === 0) ws.rows.delete(rowIdx);
1199
+ }
1200
+ for (const entry of snap) {
1201
+ const { row, col, cell } = entry;
1202
+ const newCell = setCell(dest, row + dr, col + dc, cell.value, cell.styleId);
1203
+ if (sameSheet) {
1204
+ if (cell.hyperlinkId !== void 0) newCell.hyperlinkId = cell.hyperlinkId;
1205
+ if (cell.commentId !== void 0) newCell.commentId = cell.commentId;
1206
+ }
1207
+ }
1208
+ return snap.length;
1209
+ }
1210
+ /**
1211
+ * Enumerate the populated cells of a row in column order. Unlike {@link
1212
+ * getRowValues}, this skips empty columns and yields the cell objects (not just
1213
+ * their values). Returns `[]` when the row is absent or empty.
1214
+ */
1215
+ function getCellsInRow(ws, row) {
1216
+ const rowMap = ws.rows.get(row);
1217
+ if (!rowMap || rowMap.size === 0) return [];
1218
+ const sortedCols = [...rowMap.keys()].sort((a, b) => a - b);
1219
+ const out = [];
1220
+ for (const col of sortedCols) {
1221
+ const cell = rowMap.get(col);
1222
+ if (cell) out.push(cell);
1223
+ }
1224
+ return out;
1225
+ }
1226
+ /**
1227
+ * Enumerate the populated cells of a column in row order. Walks the row map and
1228
+ * collects whichever rows carry the column. Returns `[]` when the worksheet has
1229
+ * no cell in that column.
1230
+ */
1231
+ function getCellsInColumn(ws, col) {
1232
+ const sortedRows = [...ws.rows.keys()].sort((a, b) => a - b);
1233
+ const out = [];
1234
+ for (const r of sortedRows) {
1235
+ const cell = ws.rows.get(r)?.get(col);
1236
+ if (cell) out.push(cell);
1237
+ }
1238
+ return out;
1239
+ }
1240
+ /**
1241
+ * Look up the ColumnDimension covering `col`. The search walks every registered
1242
+ * entry's `min..max` range; that's fine for the typical spreadsheet (a handful
1243
+ * of column entries) and stays simple.
1244
+ */
1245
+ function getColumnDimension(ws, col) {
1246
+ for (const dim of ws.columnDimensions.values()) if (col >= dim.min && col <= dim.max) return dim;
1247
+ }
1248
+ /**
1249
+ * Set a single-column ColumnDimension entry covering `col`. Shadows any
1250
+ * existing run that overlaps — runs are not split for now (callers that need
1251
+ * range-spanning entries can write directly into `ws.columnDimensions`).
1252
+ */
1253
+ function setColumnDimension(ws, col, opts) {
1254
+ validateRowCol(1, col);
1255
+ for (const [key, dim] of ws.columnDimensions) if (col >= dim.min && col <= dim.max) ws.columnDimensions.delete(key);
1256
+ const entry = makeColumnDimension(col, opts);
1257
+ ws.columnDimensions.set(col, entry);
1258
+ return entry;
1259
+ }
1260
+ /** Convenience: set a column's width, leaving other fields untouched. */
1261
+ function setColumnWidth(ws, col, width) {
1262
+ return setColumnDimension(ws, col, {
1263
+ ...getColumnDimension(ws, col),
1264
+ width,
1265
+ customWidth: true
1266
+ });
1267
+ }
1268
+ /** Convenience: hide a column. */
1269
+ function hideColumn(ws, col) {
1270
+ return setColumnDimension(ws, col, {
1271
+ ...getColumnDimension(ws, col),
1272
+ hidden: true
1273
+ });
1274
+ }
1275
+ /**
1276
+ * Convenience: unhide a column. Drops the `hidden` flag from the column's
1277
+ * dimension entry (and removes the entry altogether when no other fields
1278
+ * remain).
1279
+ */
1280
+ function unhideColumn(ws, col) {
1281
+ const existing = getColumnDimension(ws, col);
1282
+ if (!existing) return;
1283
+ const { hidden: _drop, ...rest } = existing;
1284
+ const { min: _min, max: _max, ...passthrough } = rest;
1285
+ if (Object.keys(passthrough).length === 0) ws.columnDimensions.delete(existing.min);
1286
+ else setColumnDimension(ws, col, passthrough);
1287
+ }
1288
+ /** Bulk-hide every column in `[fromCol, toCol]`. */
1289
+ function hideColumns(ws, fromCol, toCol) {
1290
+ if (!Number.isInteger(fromCol) || !Number.isInteger(toCol) || fromCol < 1 || toCol < fromCol) throw new OpenXmlSchemaError(`hideColumns: invalid column range [${fromCol}, ${toCol}]`);
1291
+ for (let c = fromCol; c <= toCol; c++) hideColumn(ws, c);
1292
+ }
1293
+ /** Bulk-unhide every column in `[fromCol, toCol]`. */
1294
+ function unhideColumns(ws, fromCol, toCol) {
1295
+ if (!Number.isInteger(fromCol) || !Number.isInteger(toCol) || fromCol < 1 || toCol < fromCol) throw new OpenXmlSchemaError(`unhideColumns: invalid column range [${fromCol}, ${toCol}]`);
1296
+ for (let c = fromCol; c <= toCol; c++) unhideColumn(ws, c);
1297
+ }
1298
+ /**
1299
+ * Set the default column width (characters) for cells without an explicit
1300
+ * ColumnDimension entry. Mirrors Excel's "Default Width" dialog. Pass
1301
+ * `undefined` to clear.
1302
+ */
1303
+ function setDefaultColumnWidth(ws, width) {
1304
+ if (width === void 0) {
1305
+ delete ws.defaultColumnWidth;
1306
+ return;
1307
+ }
1308
+ if (!Number.isFinite(width) || width < 0) throw new OpenXmlSchemaError(`setDefaultColumnWidth: width must be a non-negative number; got ${width}`);
1309
+ ws.defaultColumnWidth = width;
1310
+ }
1311
+ /**
1312
+ * Set the default row height (points) for rows without an explicit RowDimension
1313
+ * entry. Mirrors Excel's "Default Row Height" dialog. Pass `undefined` to
1314
+ * clear.
1315
+ */
1316
+ function setDefaultRowHeight(ws, height) {
1317
+ if (height === void 0) {
1318
+ delete ws.defaultRowHeight;
1319
+ return;
1320
+ }
1321
+ if (!Number.isFinite(height) || height < 0) throw new OpenXmlSchemaError(`setDefaultRowHeight: height must be a non-negative number; got ${height}`);
1322
+ ws.defaultRowHeight = height;
1323
+ }
1324
+ const validateOutlineLevel = (level) => {
1325
+ if (!Number.isInteger(level) || level < 1 || level > 7) throw new OpenXmlSchemaError(`outlineLevel must be an integer in [1, 7]; got ${level}`);
1326
+ };
1327
+ /**
1328
+ * Mirror Excel's "Data → Group → Rows" by stamping every row in `[fromRow,
1329
+ * toRow]` with an outline depth of `level` (default 1). Allocates a
1330
+ * RowDimension for each row that doesn't already have one. Ungroup with {@link
1331
+ * ungroupRows}.
1332
+ */
1333
+ function groupRows(ws, fromRow, toRow, level = 1) {
1334
+ validateOutlineLevel(level);
1335
+ if (!Number.isInteger(fromRow) || !Number.isInteger(toRow) || fromRow < 1 || toRow < fromRow) throw new OpenXmlSchemaError(`groupRows: invalid row range [${fromRow}, ${toRow}]`);
1336
+ for (let r = fromRow; r <= toRow; r++) {
1337
+ const existing = getRowDimension(ws, r) ?? {};
1338
+ setRowDimension(ws, r, {
1339
+ ...existing,
1340
+ outlineLevel: level
1341
+ });
1342
+ }
1343
+ }
1344
+ /**
1345
+ * Drop the outline grouping for every row in `[fromRow, toRow]`. Removes the
1346
+ * `outlineLevel` field from each affected RowDimension.
1347
+ */
1348
+ function ungroupRows(ws, fromRow, toRow) {
1349
+ if (!Number.isInteger(fromRow) || !Number.isInteger(toRow) || fromRow < 1 || toRow < fromRow) throw new OpenXmlSchemaError(`ungroupRows: invalid row range [${fromRow}, ${toRow}]`);
1350
+ for (let r = fromRow; r <= toRow; r++) {
1351
+ const existing = ws.rowDimensions.get(r);
1352
+ if (!existing) continue;
1353
+ const { outlineLevel: _drop, ...rest } = existing;
1354
+ if (Object.keys(rest).length === 0) ws.rowDimensions.delete(r);
1355
+ else ws.rowDimensions.set(r, rest);
1356
+ }
1357
+ }
1358
+ /**
1359
+ * Mirror Excel's "Data → Group → Columns" by stamping every column in
1360
+ * `[fromCol, toCol]` with an outline depth of `level` (default 1).
1361
+ */
1362
+ function groupColumns(ws, fromCol, toCol, level = 1) {
1363
+ validateOutlineLevel(level);
1364
+ if (!Number.isInteger(fromCol) || !Number.isInteger(toCol) || fromCol < 1 || toCol < fromCol) throw new OpenXmlSchemaError(`groupColumns: invalid column range [${fromCol}, ${toCol}]`);
1365
+ for (let c = fromCol; c <= toCol; c++) {
1366
+ const existing = getColumnDimension(ws, c);
1367
+ setColumnDimension(ws, c, {
1368
+ ...existing,
1369
+ outlineLevel: level
1370
+ });
1371
+ }
1372
+ }
1373
+ /**
1374
+ * Drop the outline grouping for every column in `[fromCol, toCol]`. Removes the
1375
+ * `outlineLevel` field from each affected ColumnDimension.
1376
+ */
1377
+ function ungroupColumns(ws, fromCol, toCol) {
1378
+ if (!Number.isInteger(fromCol) || !Number.isInteger(toCol) || fromCol < 1 || toCol < fromCol) throw new OpenXmlSchemaError(`ungroupColumns: invalid column range [${fromCol}, ${toCol}]`);
1379
+ for (let c = fromCol; c <= toCol; c++) {
1380
+ const existing = getColumnDimension(ws, c);
1381
+ if (!existing) continue;
1382
+ const { outlineLevel: _drop, ...rest } = existing;
1383
+ const { min: _min, max: _max, ...passthrough } = rest;
1384
+ if (Object.keys(passthrough).length === 0) ws.columnDimensions.delete(existing.min);
1385
+ else setColumnDimension(ws, c, passthrough);
1386
+ }
1387
+ }
1388
+ /**
1389
+ * Collapse a row outline group: hide every row in `[fromRow, toRow]` and mark
1390
+ * them `collapsed: true`. Mirrors Excel's `−` button on a grouped row strip.
1391
+ * Rows must already carry an `outlineLevel` from {@link groupRows} for the
1392
+ * collapse to render correctly.
1393
+ */
1394
+ function collapseRowGroup(ws, fromRow, toRow) {
1395
+ if (!Number.isInteger(fromRow) || !Number.isInteger(toRow) || fromRow < 1 || toRow < fromRow) throw new OpenXmlSchemaError(`collapseRowGroup: invalid row range [${fromRow}, ${toRow}]`);
1396
+ for (let r = fromRow; r <= toRow; r++) {
1397
+ const existing = getRowDimension(ws, r) ?? {};
1398
+ setRowDimension(ws, r, {
1399
+ ...existing,
1400
+ hidden: true,
1401
+ collapsed: true
1402
+ });
1403
+ }
1404
+ }
1405
+ /**
1406
+ * Expand a row outline group: drop the `hidden` and `collapsed` flags on every
1407
+ * row in `[fromRow, toRow]`. Leaves `outlineLevel` and other dimensions intact.
1408
+ */
1409
+ function expandRowGroup(ws, fromRow, toRow) {
1410
+ if (!Number.isInteger(fromRow) || !Number.isInteger(toRow) || fromRow < 1 || toRow < fromRow) throw new OpenXmlSchemaError(`expandRowGroup: invalid row range [${fromRow}, ${toRow}]`);
1411
+ for (let r = fromRow; r <= toRow; r++) {
1412
+ const existing = ws.rowDimensions.get(r);
1413
+ if (!existing) continue;
1414
+ const { hidden: _h, collapsed: _c, ...rest } = existing;
1415
+ if (Object.keys(rest).length === 0) ws.rowDimensions.delete(r);
1416
+ else ws.rowDimensions.set(r, rest);
1417
+ }
1418
+ }
1419
+ /**
1420
+ * Collapse a column outline group: hide + mark `collapsed: true` for every
1421
+ * column in `[fromCol, toCol]`. Columns must already carry an `outlineLevel`
1422
+ * from {@link groupColumns} for the collapse to render correctly.
1423
+ */
1424
+ function collapseColumnGroup(ws, fromCol, toCol) {
1425
+ if (!Number.isInteger(fromCol) || !Number.isInteger(toCol) || fromCol < 1 || toCol < fromCol) throw new OpenXmlSchemaError(`collapseColumnGroup: invalid column range [${fromCol}, ${toCol}]`);
1426
+ for (let c = fromCol; c <= toCol; c++) {
1427
+ const existing = getColumnDimension(ws, c);
1428
+ setColumnDimension(ws, c, {
1429
+ ...existing,
1430
+ hidden: true,
1431
+ collapsed: true
1432
+ });
1433
+ }
1434
+ }
1435
+ /**
1436
+ * Expand a column outline group: drop `hidden` and `collapsed` from every
1437
+ * column in `[fromCol, toCol]`. Leaves `outlineLevel` intact.
1438
+ */
1439
+ function expandColumnGroup(ws, fromCol, toCol) {
1440
+ if (!Number.isInteger(fromCol) || !Number.isInteger(toCol) || fromCol < 1 || toCol < fromCol) throw new OpenXmlSchemaError(`expandColumnGroup: invalid column range [${fromCol}, ${toCol}]`);
1441
+ for (let c = fromCol; c <= toCol; c++) {
1442
+ const existing = getColumnDimension(ws, c);
1443
+ if (!existing) continue;
1444
+ const { hidden: _h, collapsed: _coll, ...rest } = existing;
1445
+ const { min: _min, max: _max, ...passthrough } = rest;
1446
+ if (Object.keys(passthrough).length === 0) ws.columnDimensions.delete(existing.min);
1447
+ else setColumnDimension(ws, c, passthrough);
1448
+ }
1449
+ }
1450
+ /**
1451
+ * Per-cell effective string length, optionally scaled by the cell's font size
1452
+ * relative to Excel's default 11pt baseline.
1453
+ */
1454
+ const effectiveLength = (cell, wb) => {
1455
+ const len = cellValueAsString(cell.value).length;
1456
+ if (!wb) return len;
1457
+ const fontId = wb.styles.cellXfs[cell.styleId]?.fontId ?? 0;
1458
+ return len * ((wb.styles.fonts[fontId]?.size ?? 11) / 11);
1459
+ };
1460
+ /**
1461
+ * Approximate autofit for every column with at least one populated cell. Walks
1462
+ * the worksheet once collecting per-column widest-length + applies {@link
1463
+ * autofitColumn} per column. `opts.workbook` enables font-size-aware scaling;
1464
+ * without it the helper falls back to plain string length.
1465
+ */
1466
+ function autofitColumns(ws, opts = {}) {
1467
+ const padding = opts.padding ?? 2;
1468
+ const minWidth = opts.min ?? 4;
1469
+ const maxWidth = Math.min(opts.max ?? 80, 255);
1470
+ const widest = /* @__PURE__ */ new Map();
1471
+ for (const rowMap of ws.rows.values()) for (const [col, cell] of rowMap) {
1472
+ const len = effectiveLength(cell, opts.workbook);
1473
+ if (len > (widest.get(col) ?? 0)) widest.set(col, len);
1474
+ }
1475
+ for (const [col, w] of widest) {
1476
+ if (w === 0) continue;
1477
+ setColumnWidth(ws, col, Math.max(minWidth, Math.min(maxWidth, w + padding)));
1478
+ }
1479
+ }
1480
+ /**
1481
+ * Set widths for many columns in one call. `widths` maps either:
1482
+ * - an array `[12, 16, 20]` interpreted positionally starting at
1483
+ * column `startCol` (default 1), or
1484
+ * - a `Record<number, number>` keyed by 1-based column index.
1485
+ * Each entry sets `customWidth: true`.
1486
+ */
1487
+ function setColumnWidths(ws, widths, startCol = 1) {
1488
+ if (Array.isArray(widths)) for (let i = 0; i < widths.length; i++) {
1489
+ const w = widths[i];
1490
+ if (typeof w !== "number" || !Number.isFinite(w)) continue;
1491
+ setColumnWidth(ws, startCol + i, w);
1492
+ }
1493
+ else for (const [k, w] of Object.entries(widths)) {
1494
+ const col = Number.parseInt(k, 10);
1495
+ if (!Number.isInteger(col) || col < 1) continue;
1496
+ if (typeof w !== "number" || !Number.isFinite(w)) continue;
1497
+ setColumnWidth(ws, col, w);
1498
+ }
1499
+ }
1500
+ /** Look up a row's dimension entry. */
1501
+ function getRowDimension(ws, row) {
1502
+ return ws.rowDimensions.get(row);
1503
+ }
1504
+ function setRowDimension(ws, row, opts) {
1505
+ validateRowCol(row, 1);
1506
+ const entry = makeRowDimension(opts);
1507
+ ws.rowDimensions.set(row, entry);
1508
+ return entry;
1509
+ }
1510
+ /** Convenience: set a row's height, marking customHeight=true. */
1511
+ function setRowHeight(ws, row, height) {
1512
+ return setRowDimension(ws, row, {
1513
+ ...getRowDimension(ws, row),
1514
+ height,
1515
+ customHeight: true
1516
+ });
1517
+ }
1518
+ /**
1519
+ * Set heights for many rows in one call. `heights` accepts an array (positional
1520
+ * from `startRow`, default 1) or a `Record<number, number>` keyed by 1-based
1521
+ * row index. Each entry sets `customHeight: true`.
1522
+ */
1523
+ function setRowHeights(ws, heights, startRow = 1) {
1524
+ if (Array.isArray(heights)) for (let i = 0; i < heights.length; i++) {
1525
+ const h = heights[i];
1526
+ if (typeof h !== "number" || !Number.isFinite(h)) continue;
1527
+ setRowHeight(ws, startRow + i, h);
1528
+ }
1529
+ else for (const [k, h] of Object.entries(heights)) {
1530
+ const row = Number.parseInt(k, 10);
1531
+ if (!Number.isInteger(row) || row < 1) continue;
1532
+ if (typeof h !== "number" || !Number.isFinite(h)) continue;
1533
+ setRowHeight(ws, row, h);
1534
+ }
1535
+ }
1536
+ /** Convenience: hide a row. */
1537
+ function hideRow(ws, row) {
1538
+ return setRowDimension(ws, row, {
1539
+ ...getRowDimension(ws, row),
1540
+ hidden: true
1541
+ });
1542
+ }
1543
+ /**
1544
+ * Convenience: unhide a row. Drops the `hidden` flag from the row's dimension
1545
+ * entry (and removes the entry altogether when no other fields remain).
1546
+ */
1547
+ function unhideRow(ws, row) {
1548
+ const existing = ws.rowDimensions.get(row);
1549
+ if (!existing) return;
1550
+ const { hidden: _drop, ...rest } = existing;
1551
+ if (Object.keys(rest).length === 0) ws.rowDimensions.delete(row);
1552
+ else ws.rowDimensions.set(row, rest);
1553
+ }
1554
+ /** Bulk-hide every row in `[fromRow, toRow]`. */
1555
+ function hideRows(ws, fromRow, toRow) {
1556
+ if (!Number.isInteger(fromRow) || !Number.isInteger(toRow) || fromRow < 1 || toRow < fromRow) throw new OpenXmlSchemaError(`hideRows: invalid row range [${fromRow}, ${toRow}]`);
1557
+ for (let r = fromRow; r <= toRow; r++) hideRow(ws, r);
1558
+ }
1559
+ /** Bulk-unhide every row in `[fromRow, toRow]`. */
1560
+ function unhideRows(ws, fromRow, toRow) {
1561
+ if (!Number.isInteger(fromRow) || !Number.isInteger(toRow) || fromRow < 1 || toRow < fromRow) throw new OpenXmlSchemaError(`unhideRows: invalid row range [${fromRow}, ${toRow}]`);
1562
+ for (let r = fromRow; r <= toRow; r++) unhideRow(ws, r);
1563
+ }
1564
+ /**
1565
+ * Replace any prior hyperlink on the same `ref` with the given options. Pass `{
1566
+ * target }` for an external URL, `{ location }` for an internal jump, or both.
1567
+ * Returns the resulting Hyperlink record.
1568
+ */
1569
+ function setHyperlink(ws, ref, opts) {
1570
+ if (opts.target === void 0 && opts.location === void 0) throw new OpenXmlSchemaError("setHyperlink: one of target / location is required");
1571
+ removeHyperlink(ws, ref);
1572
+ const hl = makeHyperlink({
1573
+ ref,
1574
+ ...opts
1575
+ });
1576
+ ws.hyperlinks.push(hl);
1577
+ return hl;
1578
+ }
1579
+ /** Remove the hyperlink registered against `ref`. Returns true if anything was removed. */
1580
+ function removeHyperlink(ws, ref) {
1581
+ const i = ws.hyperlinks.findIndex((h) => h.ref === ref);
1582
+ if (i < 0) return false;
1583
+ ws.hyperlinks.splice(i, 1);
1584
+ return true;
1585
+ }
1586
+ /** Drop every hyperlink on the worksheet. Returns the count removed. */
1587
+ function removeAllHyperlinks(ws) {
1588
+ const n = ws.hyperlinks.length;
1589
+ ws.hyperlinks = [];
1590
+ return n;
1591
+ }
1592
+ /** Read-only snapshot of every hyperlink on the sheet. */
1593
+ function listHyperlinks(ws) {
1594
+ return ws.hyperlinks;
1595
+ }
1596
+ /**
1597
+ * Resolve a cell to its hyperlink (if any). Walks every hyperlink entry on the
1598
+ * worksheet and returns the first whose `ref` (a single cell `"A1"` or a range
1599
+ * `"A1:B5"`) covers the cell's coordinate. Returns `undefined` when no entry
1600
+ * matches.
1601
+ */
1602
+ function getCellHyperlink(ws, c) {
1603
+ for (const h of ws.hyperlinks) if (rangeContainsCell(parseRange(h.ref), c.row, c.col)) return h;
1604
+ }
1605
+ /**
1606
+ * Resolve a cell to its legacy comment (if any). Same matching rule as {@link
1607
+ * getCellHyperlink} — the comment's `ref` may be a single cell or a range, and
1608
+ * the first containing entry wins.
1609
+ */
1610
+ function getCellComment(ws, c) {
1611
+ for (const cm of ws.legacyComments) if (rangeContainsCell(parseRange(cm.ref), c.row, c.col)) return cm;
1612
+ }
1613
+ /** Append a DataValidation entry. */
1614
+ function addDataValidation(ws, dv) {
1615
+ ws.dataValidations.push(dv);
1616
+ return dv;
1617
+ }
1618
+ /** Drop every validation whose sqref overlaps `ref` (string parse). Returns count removed. */
1619
+ function removeDataValidations(ws, predicate) {
1620
+ const before = ws.dataValidations.length;
1621
+ ws.dataValidations = ws.dataValidations.filter((dv) => !predicate(dv));
1622
+ return before - ws.dataValidations.length;
1623
+ }
1624
+ /** Read-only snapshot of every data validation block on the sheet. */
1625
+ function listDataValidations(ws) {
1626
+ return ws.dataValidations;
1627
+ }
1628
+ /** Drop every data validation block on the worksheet. Returns the count removed. */
1629
+ function removeAllDataValidations(ws) {
1630
+ const n = ws.dataValidations.length;
1631
+ ws.dataValidations = [];
1632
+ return n;
1633
+ }
1634
+ /** Set or replace the worksheet's AutoFilter. Pass `undefined` to clear. */
1635
+ function setAutoFilter(ws, filter) {
1636
+ if (filter === void 0) {
1637
+ delete ws.autoFilter;
1638
+ return;
1639
+ }
1640
+ ws.autoFilter = filter;
1641
+ }
1642
+ /** Read the current AutoFilter, if any. */
1643
+ function getAutoFilter(ws) {
1644
+ return ws.autoFilter;
1645
+ }
1646
+ /** Append a table. The id and displayName must be workbook-unique — the caller is responsible. */
1647
+ function addTable(ws, table) {
1648
+ ws.tables.push(table);
1649
+ return table;
1650
+ }
1651
+ /** Look up a table by displayName. */
1652
+ function getTable(ws, displayName) {
1653
+ return ws.tables.find((t) => t.displayName === displayName);
1654
+ }
1655
+ /** Read-only snapshot of every Excel table defined on the sheet. */
1656
+ function listTables(ws) {
1657
+ return ws.tables;
1658
+ }
1659
+ /** Drop a table by displayName. Returns true when something was removed. */
1660
+ function removeTable(ws, displayName) {
1661
+ const i = ws.tables.findIndex((t) => t.displayName === displayName);
1662
+ if (i < 0) return false;
1663
+ ws.tables.splice(i, 1);
1664
+ return true;
1665
+ }
1666
+ /** Drop every Excel table on the worksheet. Returns the count removed. */
1667
+ function removeAllTables(ws) {
1668
+ const n = ws.tables.length;
1669
+ ws.tables = [];
1670
+ return n;
1671
+ }
1672
+ /** Add or replace the comment at `ref`. */
1673
+ function setComment(ws, opts) {
1674
+ const i = ws.legacyComments.findIndex((c) => c.ref === opts.ref);
1675
+ const c = makeLegacyComment(opts);
1676
+ if (i < 0) ws.legacyComments.push(c);
1677
+ else ws.legacyComments[i] = c;
1678
+ return c;
1679
+ }
1680
+ /** Read-only snapshot of every legacy comment on the sheet. */
1681
+ function listComments(ws) {
1682
+ return ws.legacyComments;
1683
+ }
1684
+ /** Drop every legacy comment on the worksheet. Returns the count removed. */
1685
+ function removeAllComments(ws) {
1686
+ const n = ws.legacyComments.length;
1687
+ ws.legacyComments = [];
1688
+ return n;
1689
+ }
1690
+ /** Append a conditional formatting block. */
1691
+ function addConditionalFormatting(ws, cf) {
1692
+ ws.conditionalFormatting.push(cf);
1693
+ return cf;
1694
+ }
1695
+ /** Drop every conditional-formatting block on the worksheet. Returns the count removed. */
1696
+ function removeAllConditionalFormatting(ws) {
1697
+ const n = ws.conditionalFormatting.length;
1698
+ ws.conditionalFormatting = [];
1699
+ return n;
1700
+ }
1701
+ /** Pin a cell to the Watch Window. Returns the pushed entry. */
1702
+ function addCellWatch(ws, watch) {
1703
+ ws.cellWatches.push(watch);
1704
+ return watch;
1705
+ }
1706
+ /** Remove cell watches matching `predicate`. Returns the count removed. */
1707
+ function removeCellWatches(ws, predicate) {
1708
+ const before = ws.cellWatches.length;
1709
+ ws.cellWatches = ws.cellWatches.filter((w) => !predicate(w));
1710
+ return before - ws.cellWatches.length;
1711
+ }
1712
+ /** Append an ignored-error region. */
1713
+ function addIgnoredError(ws, ie) {
1714
+ ws.ignoredErrors.push(ie);
1715
+ return ie;
1716
+ }
1717
+ /** Remove ignored-error entries matching `predicate`. Returns the count removed. */
1718
+ function removeIgnoredErrors(ws, predicate) {
1719
+ const before = ws.ignoredErrors.length;
1720
+ ws.ignoredErrors = ws.ignoredErrors.filter((ie) => !predicate(ie));
1721
+ return before - ws.ignoredErrors.length;
1722
+ }
1723
+ //#endregion
1724
+ export { iterRows as $, makeRowDimension as $t, getCellsInRow as A, unionRange as An, setRangeValues as At, getPopulatedRowIndices as B, unhideColumns as Bt, getAutoFilter as C, parseRange as Cn, setColumnWidth as Ct, getCellHyperlink as D, rangeToString as Dn, setDefaultRowHeight as Dt, getCellComment as E, rangeContainsRange as En, setDefaultColumnWidth as Et, getMaxRow as F, setSheetViewMode as Ft, groupRows as G, writeRange as Gt, getRowDimension as H, unhideRows as Ht, getMergedCells as I, setSheetZoom as It, hideRow as J, freezePaneRef as Jt, hideColumn as K, makeSheetProperties as Kt, getMergedRangeAt as L, ungroupColumns as Lt, getDataExtent as M, setRowHeight as Mt, getFreezePanes as N, setRowHeights as Nt, getCellsInColumn as O, rangesOverlap as On, setFreezePanes as Ot, getMaxCol as P, setSheetTabColor as Pt, iterCells as Q, makeColumnDimension as Qt, getNonEmptyCellCount as R, ungroupRows as Rt, freezePanes as S, parseMultiCellRange as Sn, setColumnDimension as St, getCellByCoord as T, rangeContainsCell as Tn, setComment as Tt, getTable as U, unmergeCells as Ut, getRangeValues as V, unhideRow as Vt, groupColumns as W, unmergeCellsAt as Wt, isMergedCell as X, makeSheetView as Xt, hideRows as Y, makeFreezePane as Yt, isWorksheetEmpty as Z, makeHyperlink as Zt, countCellsByKind as _, intersectionRange as _n, removeIgnoredErrors as _t, addTable as a, hexToHsl as an, makeWorksheet as at, expandRowGroup as b, multiCellRangeContainsCell as bn, setCell as bt, applyToRange as c, luminance as cn, removeAllComments as ct, clearAllCells as d, normaliseRgb as dn, removeAllHyperlinks as dt, adjustLightness as en, iterValues as et, clearRange as f, pickReadableTextColor as fn, removeAllMergedRanges as ft, countCells as g, expandRangeStr as gn, removeHyperlink as gt, copyRange as h, rotateHue as hn, removeDataValidations as ht, addIgnoredError as i, darken as in, listTables as it, getColumnDimension as j, setRowDimension as jt, getCellsInRange as k, shiftRange as kn, setHyperlink as kt, autofitColumns as l, makeColor as ln, removeAllConditionalFormatting as lt, collapseRowGroup as m, rgbColor as mn, removeCellWatches as mt, addConditionalFormatting as n, colorToHex as nn, listDataValidations as nt, appendRow as o, hslToHex as on, mergeCells as ot, collapseColumnGroup as p, resolveIndexedColor as pn, removeAllTables as pt, hideColumns as q, makeLegacyComment as qt, addDataValidation as r, contrastRatio as rn, listHyperlinks as rt, appendRows as s, lighten as sn, moveRange as st, addCellWatch as t, adjustSaturation as tn, listComments as tt, classifyCellValue as u, mixColors as un, removeAllDataValidations as ut, deleteCell as v, isCellInRange as vn, removeTable as vt, getCell as w, rangeArea as wn, setColumnWidths as wt, findCells as x, multiCellRangeToString as xn, setCellByCoord as xt, expandColumnGroup as y, isRangeInRange as yn, setAutoFilter as yt, getPopulatedColumnIndices as z, unhideColumn as zt };
1725
+
1726
+ //# sourceMappingURL=worksheet-CmCNoIgD.mjs.map