@shbernal/ts-xlsx 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +230 -0
  3. package/dist/core/address.d.ts +49 -0
  4. package/dist/core/address.js +125 -0
  5. package/dist/core/autofilter.d.ts +55 -0
  6. package/dist/core/autofilter.js +48 -0
  7. package/dist/core/cell.d.ts +132 -0
  8. package/dist/core/cell.js +249 -0
  9. package/dist/core/color-resolution.d.ts +58 -0
  10. package/dist/core/color-resolution.js +170 -0
  11. package/dist/core/column.d.ts +87 -0
  12. package/dist/core/column.js +167 -0
  13. package/dist/core/comment-thread.d.ts +116 -0
  14. package/dist/core/comment-thread.js +62 -0
  15. package/dist/core/conditional-formatting-overlay.d.ts +15 -0
  16. package/dist/core/conditional-formatting-overlay.js +24 -0
  17. package/dist/core/conditional-formatting.d.ts +70 -0
  18. package/dist/core/conditional-formatting.js +35 -0
  19. package/dist/core/containers.d.ts +2 -0
  20. package/dist/core/containers.js +25 -0
  21. package/dist/core/data-validation-overlay.d.ts +28 -0
  22. package/dist/core/data-validation-overlay.js +56 -0
  23. package/dist/core/data-validation.d.ts +38 -0
  24. package/dist/core/data-validation.js +13 -0
  25. package/dist/core/date.d.ts +25 -0
  26. package/dist/core/date.js +58 -0
  27. package/dist/core/formula.d.ts +49 -0
  28. package/dist/core/formula.js +319 -0
  29. package/dist/core/grid-edits.d.ts +22 -0
  30. package/dist/core/grid-edits.js +206 -0
  31. package/dist/core/image.d.ts +76 -0
  32. package/dist/core/image.js +65 -0
  33. package/dist/core/internal.d.ts +11 -0
  34. package/dist/core/internal.js +29 -0
  35. package/dist/core/merge.d.ts +13 -0
  36. package/dist/core/merge.js +26 -0
  37. package/dist/core/modern-functions.d.ts +1 -0
  38. package/dist/core/modern-functions.js +165 -0
  39. package/dist/core/page-setup.d.ts +94 -0
  40. package/dist/core/page-setup.js +6 -0
  41. package/dist/core/pivot-table.d.ts +113 -0
  42. package/dist/core/pivot-table.js +249 -0
  43. package/dist/core/preserved.d.ts +58 -0
  44. package/dist/core/preserved.js +5 -0
  45. package/dist/core/protection.d.ts +67 -0
  46. package/dist/core/protection.js +68 -0
  47. package/dist/core/range.d.ts +112 -0
  48. package/dist/core/range.js +283 -0
  49. package/dist/core/row.d.ts +73 -0
  50. package/dist/core/row.js +135 -0
  51. package/dist/core/style.d.ts +246 -0
  52. package/dist/core/style.js +125 -0
  53. package/dist/core/table-style.d.ts +67 -0
  54. package/dist/core/table-style.js +90 -0
  55. package/dist/core/table.d.ts +194 -0
  56. package/dist/core/table.js +291 -0
  57. package/dist/core/theme.d.ts +90 -0
  58. package/dist/core/theme.js +272 -0
  59. package/dist/core/value.d.ts +115 -0
  60. package/dist/core/value.js +144 -0
  61. package/dist/core/workbook-protection.d.ts +27 -0
  62. package/dist/core/workbook-protection.js +25 -0
  63. package/dist/core/workbook.d.ts +528 -0
  64. package/dist/core/workbook.js +732 -0
  65. package/dist/core/worksheet-model.d.ts +34 -0
  66. package/dist/core/worksheet-model.js +100 -0
  67. package/dist/core/worksheet.d.ts +647 -0
  68. package/dist/core/worksheet.js +1025 -0
  69. package/dist/customui/errors.d.ts +15 -0
  70. package/dist/customui/errors.js +15 -0
  71. package/dist/customui/index.d.ts +2 -0
  72. package/dist/customui/index.js +7 -0
  73. package/dist/customui/ribbon.d.ts +86 -0
  74. package/dist/customui/ribbon.js +180 -0
  75. package/dist/entries/core.d.ts +23 -0
  76. package/dist/entries/core.js +20 -0
  77. package/dist/entries/csv.d.ts +2 -0
  78. package/dist/entries/csv.js +6 -0
  79. package/dist/entries/customui.d.ts +1 -0
  80. package/dist/entries/customui.js +3 -0
  81. package/dist/entries/errors.d.ts +7 -0
  82. package/dist/entries/errors.js +20 -0
  83. package/dist/entries/vba.d.ts +2 -0
  84. package/dist/entries/vba.js +7 -0
  85. package/dist/entries/xlsb.d.ts +1 -0
  86. package/dist/entries/xlsb.js +7 -0
  87. package/dist/entries/xlsx.d.ts +5 -0
  88. package/dist/entries/xlsx.js +18 -0
  89. package/dist/errors.d.ts +65 -0
  90. package/dist/errors.js +66 -0
  91. package/dist/index.d.ts +7 -0
  92. package/dist/index.js +19 -0
  93. package/dist/io/csv/read.d.ts +15 -0
  94. package/dist/io/csv/read.js +113 -0
  95. package/dist/io/csv/write.d.ts +28 -0
  96. package/dist/io/csv/write.js +124 -0
  97. package/dist/io/opc/errors.d.ts +46 -0
  98. package/dist/io/opc/errors.js +49 -0
  99. package/dist/io/opc/inflate.d.ts +15 -0
  100. package/dist/io/opc/inflate.js +91 -0
  101. package/dist/io/opc/namespaces.d.ts +18 -0
  102. package/dist/io/opc/namespaces.js +18 -0
  103. package/dist/io/opc/part-paths.d.ts +4 -0
  104. package/dist/io/opc/part-paths.js +35 -0
  105. package/dist/io/opc/read-opc.d.ts +24 -0
  106. package/dist/io/opc/read-opc.js +153 -0
  107. package/dist/io/opc/read-options.d.ts +10 -0
  108. package/dist/io/opc/read-options.js +7 -0
  109. package/dist/io/opc/rels.d.ts +15 -0
  110. package/dist/io/opc/rels.js +28 -0
  111. package/dist/io/opc/sniff-format.d.ts +27 -0
  112. package/dist/io/opc/sniff-format.js +92 -0
  113. package/dist/io/style/xf-style.d.ts +47 -0
  114. package/dist/io/style/xf-style.js +95 -0
  115. package/dist/io/xlsb/errors.d.ts +15 -0
  116. package/dist/io/xlsb/errors.js +15 -0
  117. package/dist/io/xlsb/formula.d.ts +48 -0
  118. package/dist/io/xlsb/formula.js +398 -0
  119. package/dist/io/xlsb/primitives.d.ts +86 -0
  120. package/dist/io/xlsb/primitives.js +230 -0
  121. package/dist/io/xlsb/ptg-functions.d.ts +16 -0
  122. package/dist/io/xlsb/ptg-functions.js +659 -0
  123. package/dist/io/xlsb/read-shared-strings.d.ts +2 -0
  124. package/dist/io/xlsb/read-shared-strings.js +22 -0
  125. package/dist/io/xlsb/read-styles.d.ts +4 -0
  126. package/dist/io/xlsb/read-styles.js +386 -0
  127. package/dist/io/xlsb/read-worksheet.d.ts +9 -0
  128. package/dist/io/xlsb/read-worksheet.js +301 -0
  129. package/dist/io/xlsb/read.d.ts +20 -0
  130. package/dist/io/xlsb/read.js +199 -0
  131. package/dist/io/xlsb/record-stream.d.ts +14 -0
  132. package/dist/io/xlsb/record-stream.js +56 -0
  133. package/dist/io/xlsb/record-types.d.ts +55 -0
  134. package/dist/io/xlsb/record-types.js +73 -0
  135. package/dist/io/xlsx/cell-accumulator.d.ts +25 -0
  136. package/dist/io/xlsx/cell-accumulator.js +172 -0
  137. package/dist/io/xlsx/cell-value.d.ts +32 -0
  138. package/dist/io/xlsx/cell-value.js +85 -0
  139. package/dist/io/xlsx/comments.d.ts +74 -0
  140. package/dist/io/xlsx/comments.js +243 -0
  141. package/dist/io/xlsx/conditional-formatting.d.ts +32 -0
  142. package/dist/io/xlsx/conditional-formatting.js +464 -0
  143. package/dist/io/xlsx/data-validation.d.ts +20 -0
  144. package/dist/io/xlsx/data-validation.js +258 -0
  145. package/dist/io/xlsx/edit-vba.d.ts +23 -0
  146. package/dist/io/xlsx/edit-vba.js +139 -0
  147. package/dist/io/xlsx/errors.d.ts +17 -0
  148. package/dist/io/xlsx/errors.js +17 -0
  149. package/dist/io/xlsx/hyperlinks.d.ts +42 -0
  150. package/dist/io/xlsx/hyperlinks.js +139 -0
  151. package/dist/io/xlsx/images.d.ts +34 -0
  152. package/dist/io/xlsx/images.js +218 -0
  153. package/dist/io/xlsx/namespaces.d.ts +55 -0
  154. package/dist/io/xlsx/namespaces.js +55 -0
  155. package/dist/io/xlsx/package-plan.d.ts +103 -0
  156. package/dist/io/xlsx/package-plan.js +168 -0
  157. package/dist/io/xlsx/pivot-read.d.ts +5 -0
  158. package/dist/io/xlsx/pivot-read.js +134 -0
  159. package/dist/io/xlsx/pivot.d.ts +12 -0
  160. package/dist/io/xlsx/pivot.js +141 -0
  161. package/dist/io/xlsx/read-rows.d.ts +85 -0
  162. package/dist/io/xlsx/read-rows.js +286 -0
  163. package/dist/io/xlsx/read-styles.d.ts +8 -0
  164. package/dist/io/xlsx/read-styles.js +551 -0
  165. package/dist/io/xlsx/read-worksheet.d.ts +4 -0
  166. package/dist/io/xlsx/read-worksheet.js +528 -0
  167. package/dist/io/xlsx/read.d.ts +32 -0
  168. package/dist/io/xlsx/read.js +680 -0
  169. package/dist/io/xlsx/relationships.d.ts +33 -0
  170. package/dist/io/xlsx/relationships.js +47 -0
  171. package/dist/io/xlsx/rich-runs.d.ts +13 -0
  172. package/dist/io/xlsx/rich-runs.js +59 -0
  173. package/dist/io/xlsx/rich-text.d.ts +7 -0
  174. package/dist/io/xlsx/rich-text.js +23 -0
  175. package/dist/io/xlsx/shared-formulas.d.ts +6 -0
  176. package/dist/io/xlsx/shared-formulas.js +52 -0
  177. package/dist/io/xlsx/shared-strings-read.d.ts +2 -0
  178. package/dist/io/xlsx/shared-strings-read.js +70 -0
  179. package/dist/io/xlsx/shared-strings.d.ts +17 -0
  180. package/dist/io/xlsx/shared-strings.js +50 -0
  181. package/dist/io/xlsx/sheet-properties.d.ts +13 -0
  182. package/dist/io/xlsx/sheet-properties.js +211 -0
  183. package/dist/io/xlsx/styles.d.ts +138 -0
  184. package/dist/io/xlsx/styles.js +821 -0
  185. package/dist/io/xlsx/tables.d.ts +9 -0
  186. package/dist/io/xlsx/tables.js +208 -0
  187. package/dist/io/xlsx/threaded-comments.d.ts +102 -0
  188. package/dist/io/xlsx/threaded-comments.js +308 -0
  189. package/dist/io/xlsx/workbook-xml.d.ts +12 -0
  190. package/dist/io/xlsx/workbook-xml.js +353 -0
  191. package/dist/io/xlsx/worksheet-xml.d.ts +86 -0
  192. package/dist/io/xlsx/worksheet-xml.js +643 -0
  193. package/dist/io/xlsx/write-stream.d.ts +171 -0
  194. package/dist/io/xlsx/write-stream.js +399 -0
  195. package/dist/io/xlsx/write.d.ts +81 -0
  196. package/dist/io/xlsx/write.js +396 -0
  197. package/dist/io/xlsx/x14-ext.d.ts +9 -0
  198. package/dist/io/xlsx/x14-ext.js +12 -0
  199. package/dist/vba/cfb-writer.d.ts +19 -0
  200. package/dist/vba/cfb-writer.js +277 -0
  201. package/dist/vba/cfb.d.ts +16 -0
  202. package/dist/vba/cfb.js +324 -0
  203. package/dist/vba/codepage.d.ts +18 -0
  204. package/dist/vba/codepage.js +87 -0
  205. package/dist/vba/errors.d.ts +24 -0
  206. package/dist/vba/errors.js +24 -0
  207. package/dist/vba/index.d.ts +5 -0
  208. package/dist/vba/index.js +15 -0
  209. package/dist/vba/ms-ovba.d.ts +12 -0
  210. package/dist/vba/ms-ovba.js +176 -0
  211. package/dist/vba/project-editor.d.ts +62 -0
  212. package/dist/vba/project-editor.js +377 -0
  213. package/dist/vba/project.d.ts +45 -0
  214. package/dist/vba/project.js +160 -0
  215. package/dist/vba/vba-encoding.d.ts +14 -0
  216. package/dist/vba/vba-encoding.js +39 -0
  217. package/dist/xml/errors.d.ts +15 -0
  218. package/dist/xml/errors.js +15 -0
  219. package/dist/xml/xml-read.d.ts +107 -0
  220. package/dist/xml/xml-read.js +283 -0
  221. package/dist/xml/xml.d.ts +41 -0
  222. package/dist/xml/xml.js +86 -0
  223. package/package.json +117 -0
@@ -0,0 +1,167 @@
1
+ // A handle on one column of a worksheet: its formatting and its cells, reached by column index.
2
+ //
3
+ // The other axis of `Row`, and the same contract — a live view over the worksheet's stores, position
4
+ // fixed at construction, formatting created on write rather than on read. See `core/row.ts` for why
5
+ // it is a handle rather than a record.
6
+ //
7
+ // A column carries more than a row does: besides the geometry (width, visibility, outline) it holds
8
+ // the six `CellStyle` facets as *defaults* for its cells, which is why the mirror below is twice the
9
+ // length of the row's.
10
+ import { encodeAddress, numberToColumn } from "./address.js";
11
+ import { INTERNAL } from "./internal.js";
12
+ export class Column {
13
+ #sheet;
14
+ /** 1-based column index. Fixed for this handle's lifetime. */
15
+ index;
16
+ /** @throws {RangeError} if the index is not a positive integer. */
17
+ constructor(sheet, index) {
18
+ if (!Number.isInteger(index) || index < 1) {
19
+ throw new RangeError(`column ${index} is out of bounds — columns start at 1`);
20
+ }
21
+ this.#sheet = sheet;
22
+ this.index = index;
23
+ }
24
+ /** The column's letters (`"B"`) — the spreadsheet-facing name for {@link index}. */
25
+ get letter() {
26
+ return numberToColumn(this.index);
27
+ }
28
+ /**
29
+ * The column's format record if it has one, else `undefined` — a read that never fabricates, so a
30
+ * serializer can ask every column it visits whether there are attributes to emit without giving
31
+ * each one an empty record. Read-only on purpose: {@link width} and its siblings are how a column
32
+ * is formatted, and they create the record on first write.
33
+ */
34
+ get properties() {
35
+ return this.#sheet[INTERNAL].columnPropertiesOf(this.index);
36
+ }
37
+ /**
38
+ * Stable key naming this column so a keyed-object row (see {@link Worksheet.addRow}) can place a
39
+ * value under it by name rather than position. In-memory only — never serialized to OOXML.
40
+ */
41
+ get key() {
42
+ return this.#read('key');
43
+ }
44
+ set key(key) {
45
+ this.#write('key', key);
46
+ }
47
+ /** Column width in character units; `undefined` leaves the sheet default in force. */
48
+ get width() {
49
+ return this.#read('width');
50
+ }
51
+ set width(width) {
52
+ this.#write('width', width);
53
+ }
54
+ /** Whether the column is hidden. */
55
+ get hidden() {
56
+ return this.#read('hidden');
57
+ }
58
+ set hidden(hidden) {
59
+ this.#write('hidden', hidden);
60
+ }
61
+ /** Outline (grouping) depth; 0 or `undefined` means ungrouped. */
62
+ get outlineLevel() {
63
+ return this.#read('outlineLevel');
64
+ }
65
+ set outlineLevel(outlineLevel) {
66
+ this.#write('outlineLevel', outlineLevel);
67
+ }
68
+ /** Whether this column is the collapsed summary of an outline group. */
69
+ get collapsed() {
70
+ return this.#read('collapsed');
71
+ }
72
+ set collapsed(collapsed) {
73
+ this.#write('collapsed', collapsed);
74
+ }
75
+ /** Default fill for the column's cells that set none of their own. */
76
+ get fill() {
77
+ return this.#read('fill');
78
+ }
79
+ set fill(fill) {
80
+ this.#write('fill', fill);
81
+ }
82
+ /** Default number format for the column's cells that set none of their own. */
83
+ get numFmt() {
84
+ return this.#read('numFmt');
85
+ }
86
+ set numFmt(numFmt) {
87
+ this.#write('numFmt', numFmt);
88
+ }
89
+ /** Default font for the column's cells that set none of their own. */
90
+ get font() {
91
+ return this.#read('font');
92
+ }
93
+ set font(font) {
94
+ this.#write('font', font);
95
+ }
96
+ /** Default border for the column's cells that set none of their own. */
97
+ get border() {
98
+ return this.#read('border');
99
+ }
100
+ set border(border) {
101
+ this.#write('border', border);
102
+ }
103
+ /** Default alignment for the column's cells that set none of their own. */
104
+ get alignment() {
105
+ return this.#read('alignment');
106
+ }
107
+ set alignment(alignment) {
108
+ this.#write('alignment', alignment);
109
+ }
110
+ /** Default protection flags for the column's cells that set none of their own. */
111
+ get protection() {
112
+ return this.#read('protection');
113
+ }
114
+ set protection(protection) {
115
+ this.#write('protection', protection);
116
+ }
117
+ /**
118
+ * The cell at a 1-based row number in this column, creating it on first access. Resolves through
119
+ * merges exactly as {@link Worksheet.getCell} does.
120
+ *
121
+ * @throws {RangeError} if the row is not a positive integer.
122
+ */
123
+ getCell(row) {
124
+ return this.#sheet.getCell(encodeAddress(this.index, row));
125
+ }
126
+ /**
127
+ * The column's materialised cells in ascending row order. Sparse: a row that never wrote to this
128
+ * column has no cell here.
129
+ */
130
+ get cells() {
131
+ return this.#sheet[INTERNAL].columnCells(this.index);
132
+ }
133
+ /**
134
+ * The column's values by position, index 0 being row 1. Sparse in the same way {@link cells} is:
135
+ * a row with no cell in this column is a hole, which is what distinguishes "never written" from a
136
+ * cell holding `null`.
137
+ *
138
+ * Assigning places each value it names and leaves every other row untouched, mirroring
139
+ * {@link Row.values} — a hole or an explicit `undefined` skips that row, and a shorter array does
140
+ * not clear the tail.
141
+ */
142
+ get values() {
143
+ const values = [];
144
+ for (const cell of this.cells)
145
+ values[cell.row - 1] = cell.value;
146
+ return values;
147
+ }
148
+ set values(values) {
149
+ values.forEach((value, index) => {
150
+ if (value !== undefined)
151
+ this.getCell(index + 1).value = value;
152
+ });
153
+ }
154
+ #read(key) {
155
+ return this.#sheet[INTERNAL].columnPropertiesOf(this.index)?.[key];
156
+ }
157
+ // `undefined` clears rather than stores — see the note on `Row`'s counterpart.
158
+ #write(key, value) {
159
+ if (value === undefined) {
160
+ const properties = this.#sheet[INTERNAL].columnPropertiesOf(this.index);
161
+ if (properties !== undefined)
162
+ delete properties[key];
163
+ return;
164
+ }
165
+ this.#sheet[INTERNAL].ensureColumnProperties(this.index)[key] = value;
166
+ }
167
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * A registered identity a threaded comment can point at — an author, or someone `@mentioned` in a
3
+ * message. One `<person>` of the workbook's `xl/persons/person.xml` registry.
4
+ *
5
+ * A single human legitimately has **several** entries: Excel registers a mentioned identity separately
6
+ * from that person's authoring identity, with the same {@link displayName} and {@link userId} but a
7
+ * different {@link id} and a different {@link providerId}. The id is therefore the only identity —
8
+ * see {@link Workbook.getPerson}.
9
+ */
10
+ export interface Person {
11
+ /** Brace-wrapped GUID this identity is referenced by. The only field that identifies it. */
12
+ readonly id: string;
13
+ /** The name a spreadsheet app shows — not unique, and not an identity. */
14
+ readonly displayName: string;
15
+ /** Identity-provider handle, `S::<email>::<tenant-guid>` for an AzureAD account. */
16
+ readonly userId?: string;
17
+ /** The provider that registered this entry — `AD` for a directory account, `PeoplePicker` for an
18
+ * identity interned by being mentioned. */
19
+ readonly providerId?: string;
20
+ }
21
+ /**
22
+ * An `@mention` inside a message: who was named, and the run of {@link Comment.text} that renders as the
23
+ * mention chip.
24
+ *
25
+ * The offsets are only meaningful against that exact text — shift either and a spreadsheet app
26
+ * highlights the wrong words.
27
+ */
28
+ export interface Mention {
29
+ /**
30
+ * The mentioned identity, resolved through the workbook registry. Absent when the file names an id
31
+ * the registry does not hold (a mention left dangling by a foreign generator); {@link personId}
32
+ * still says who was meant.
33
+ */
34
+ readonly person?: Person;
35
+ /** The mentioned {@link Person.id} exactly as written, so a dangling mention stays diagnosable. */
36
+ readonly personId: string;
37
+ /** Excel's own id for this mention, preserved so re-emitting it does not invent a new one. */
38
+ readonly mentionId?: string;
39
+ /** 0-based character offset into {@link Comment.text} where the mention starts. */
40
+ readonly startIndex: number;
41
+ /** Length of the mention in characters, **counting the leading `@`** (`@Grace Hopper` is 13). */
42
+ readonly length: number;
43
+ }
44
+ /** One message of a {@link CommentThread} — what a single person wrote, once. */
45
+ export interface Comment {
46
+ /** Brace-wrapped GUID identifying this message, preserved verbatim from the file. */
47
+ readonly id: string;
48
+ /**
49
+ * Who wrote it, resolved through the workbook registry. Absent when the file recorded no author or
50
+ * named an id the registry does not hold; {@link personId} distinguishes those two cases.
51
+ */
52
+ readonly author?: Person;
53
+ /** The author's {@link Person.id} exactly as written; absent when the file recorded no author. */
54
+ readonly personId?: string;
55
+ /**
56
+ * When it was written, verbatim. Excel writes local wall-clock with fractional seconds and no
57
+ * timezone (`2026-07-24T10:56:41.72`), which is not a round-trippable instant — keeping the string
58
+ * spares the reader from inventing a zone the file never stated.
59
+ */
60
+ readonly date?: string;
61
+ /** The message body as plain text. Mention chips are part of it; {@link mentions} spans it. */
62
+ readonly text: string;
63
+ /** The `@mentions` this message carries, in document order. Empty for a message that names no one. */
64
+ readonly mentions: readonly Mention[];
65
+ }
66
+ /**
67
+ * The one spelling every threaded-comment identifier must take on the wire: brace-wrapped, upper-case hex.
68
+ *
69
+ * Verified against the OOXML schema, which pins `person/@id`, a message's `id`/`personId`/`parentId`, and a
70
+ * mention's `mentionpersonId`/`mentionId` to exactly `\{[0-9A-F]{8}-…\}` — a bare GUID and a lower-case one
71
+ * are each rejected outright. So this normalises rather than merely checking: a caller passing
72
+ * `crypto.randomUUID()` gets a valid file instead of one Excel offers to repair.
73
+ *
74
+ * The authoring path alone goes through here. A file's own ids are re-emitted as the file wrote them, since
75
+ * a reader that rewrote them would break every reference pointing at them.
76
+ *
77
+ * @throws {SyntaxError} if the value is not a GUID in any spelling.
78
+ */
79
+ export declare function commentThreadGuid(value: string, what: string): string;
80
+ /**
81
+ * The largest value a mention offset can take on the wire. Verified against the OOXML schema: both
82
+ * `startIndex` and `length` are `xsd:unsignedInt`, so `4294967295` validates and `4294967296` is rejected
83
+ * as "not a valid 'UInt32' value".
84
+ *
85
+ * Wildly beyond any real message, and that is the point — the ceiling exists so a value from a hostile
86
+ * part can never reach the serialiser. JavaScript spells a large enough number in exponent form
87
+ * (`String(1e21)` is `"1e+21"`), which is not a numeric literal any schema accepts, and one invalid
88
+ * attribute is enough for Excel to offer to repair the whole conversation away.
89
+ */
90
+ export declare const MENTION_OFFSET_MAX = 4294967295;
91
+ /**
92
+ * A mention offset as the wire accepts it: a whole number within {@link MENTION_OFFSET_MAX}.
93
+ *
94
+ * The authoring path alone throws. A file's own mentions are read leniently — one carrying an unusable
95
+ * offset is dropped, keeping the message text and losing only the chip — because a foreign generator's
96
+ * arithmetic is not something a caller can fix, whereas their own is.
97
+ *
98
+ * @throws {SyntaxError} if the value is negative, fractional, or beyond the wire's ceiling.
99
+ */
100
+ export declare function commentThreadOffset(value: number, what: string): number;
101
+ /** A conversation anchored to one cell: what was asked, every reply, and whether it was resolved. */
102
+ export interface CommentThread {
103
+ /**
104
+ * A1 reference of the single cell the conversation hangs off, canonicalised — no `$` anchors, always
105
+ * a column and a row — so two anchors compare as plain strings and a writer can resolve it without
106
+ * re-validating it.
107
+ */
108
+ readonly ref: string;
109
+ /**
110
+ * Whether the conversation was marked resolved. A property of the *thread*: only the head carries
111
+ * the flag on the wire, so a reply never disagrees with the thread it belongs to.
112
+ */
113
+ readonly resolved: boolean;
114
+ /** The opening message first, then its replies in the order they were written. Never empty. */
115
+ readonly comments: readonly Comment[];
116
+ }
@@ -0,0 +1,62 @@
1
+ // Modern threaded comments in the model — the review-style conversations Excel has written since 2018:
2
+ // an anchored discussion of authored messages with timestamps, replies, a resolved state, and
3
+ // @mentions. Distinct from a legacy note (`cell.note`), which is a single anonymous annotation; Excel's
4
+ // own UI draws the same line, calling these "comments" and those "notes". A cell carries one or the
5
+ // other, never both.
6
+ //
7
+ // The wire form lives in two parts (see `io/xlsx/threaded-comments.ts`): the messages per sheet, and a
8
+ // workbook-level identity registry the messages point into. The model mirrors that split — a thread's
9
+ // authors and mentioned people are {@link Person} entries resolved through
10
+ // {@link Workbook.getPerson}, not names duplicated onto every message.
11
+ // A GUID in any of the spellings a caller plausibly has one in — braced or bare, upper or lower case.
12
+ // `crypto.randomUUID()` produces the bare lower-case form, so accepting only the canonical spelling would
13
+ // reject the one obvious way to make an id in JavaScript.
14
+ const GUID = /^\{?([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})\}?$/i;
15
+ /**
16
+ * The one spelling every threaded-comment identifier must take on the wire: brace-wrapped, upper-case hex.
17
+ *
18
+ * Verified against the OOXML schema, which pins `person/@id`, a message's `id`/`personId`/`parentId`, and a
19
+ * mention's `mentionpersonId`/`mentionId` to exactly `\{[0-9A-F]{8}-…\}` — a bare GUID and a lower-case one
20
+ * are each rejected outright. So this normalises rather than merely checking: a caller passing
21
+ * `crypto.randomUUID()` gets a valid file instead of one Excel offers to repair.
22
+ *
23
+ * The authoring path alone goes through here. A file's own ids are re-emitted as the file wrote them, since
24
+ * a reader that rewrote them would break every reference pointing at them.
25
+ *
26
+ * @throws {SyntaxError} if the value is not a GUID in any spelling.
27
+ */
28
+ export function commentThreadGuid(value, what) {
29
+ const match = GUID.exec(value.trim());
30
+ if (match === null) {
31
+ throw new SyntaxError(`${what} must be a GUID — Excel writes threaded-comment ids as ` +
32
+ `"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}" — but got "${value}"`);
33
+ }
34
+ return `{${match.slice(1).join('-').toUpperCase()}}`;
35
+ }
36
+ /**
37
+ * The largest value a mention offset can take on the wire. Verified against the OOXML schema: both
38
+ * `startIndex` and `length` are `xsd:unsignedInt`, so `4294967295` validates and `4294967296` is rejected
39
+ * as "not a valid 'UInt32' value".
40
+ *
41
+ * Wildly beyond any real message, and that is the point — the ceiling exists so a value from a hostile
42
+ * part can never reach the serialiser. JavaScript spells a large enough number in exponent form
43
+ * (`String(1e21)` is `"1e+21"`), which is not a numeric literal any schema accepts, and one invalid
44
+ * attribute is enough for Excel to offer to repair the whole conversation away.
45
+ */
46
+ export const MENTION_OFFSET_MAX = 0xffff_ffff;
47
+ /**
48
+ * A mention offset as the wire accepts it: a whole number within {@link MENTION_OFFSET_MAX}.
49
+ *
50
+ * The authoring path alone throws. A file's own mentions are read leniently — one carrying an unusable
51
+ * offset is dropped, keeping the message text and losing only the chip — because a foreign generator's
52
+ * arithmetic is not something a caller can fix, whereas their own is.
53
+ *
54
+ * @throws {SyntaxError} if the value is negative, fractional, or beyond the wire's ceiling.
55
+ */
56
+ export function commentThreadOffset(value, what) {
57
+ if (!Number.isInteger(value) || value < 0 || value > MENTION_OFFSET_MAX) {
58
+ throw new SyntaxError(`${what} must be a whole number between 0 and ${MENTION_OFFSET_MAX} — a mention's span is ` +
59
+ `written as an unsigned 32-bit integer — but got ${value}`);
60
+ }
61
+ return value;
62
+ }
@@ -0,0 +1,15 @@
1
+ import { type ConditionalFormatting } from './conditional-formatting.ts';
2
+ export declare class ConditionalFormattingOverlay {
3
+ #private;
4
+ /**
5
+ * Attach a conditional formatting to a target range. `formatting.ref` is an OOXML `sqref` — one
6
+ * range (`"A1:A10"`), a whole column, or several space-separated areas (`"A1:C1 A3:C3"`) sharing one
7
+ * rule set. The block is stored once against the range, defensively copied so the getter never hands
8
+ * back a reference into the caller's object.
9
+ */
10
+ add(formatting: ConditionalFormatting): void;
11
+ /** The conditional formattings on this sheet, each bound to its target range, in insertion order. */
12
+ get entries(): readonly ConditionalFormatting[];
13
+ /** Drop every conditional formatting, leaving the overlay empty. */
14
+ clear(): void;
15
+ }
@@ -0,0 +1,24 @@
1
+ // The conditional-formatting overlay a Worksheet owns: an insertion-ordered, defensively-copied list
2
+ // of range-bound rule sets. Kept as its own class, the sibling to {@link DataValidationOverlay}, so
3
+ // Worksheet delegates the collection's storage and cloning rather than managing the array itself.
4
+ import { cloneConditionalFormatting } from "./conditional-formatting.js";
5
+ export class ConditionalFormattingOverlay {
6
+ #entries = [];
7
+ /**
8
+ * Attach a conditional formatting to a target range. `formatting.ref` is an OOXML `sqref` — one
9
+ * range (`"A1:A10"`), a whole column, or several space-separated areas (`"A1:C1 A3:C3"`) sharing one
10
+ * rule set. The block is stored once against the range, defensively copied so the getter never hands
11
+ * back a reference into the caller's object.
12
+ */
13
+ add(formatting) {
14
+ this.#entries.push(cloneConditionalFormatting(formatting));
15
+ }
16
+ /** The conditional formattings on this sheet, each bound to its target range, in insertion order. */
17
+ get entries() {
18
+ return this.#entries;
19
+ }
20
+ /** Drop every conditional formatting, leaving the overlay empty. */
21
+ clear() {
22
+ this.#entries.length = 0;
23
+ }
24
+ }
@@ -0,0 +1,70 @@
1
+ import type { Color, DifferentialStyle } from './style.ts';
2
+ /**
3
+ * One anchor of a colour-scale, data-bar, or icon-set scale — a "conditional format value object".
4
+ * `type` names how `value` is read: a literal `num`, a `percent`/`percentile` of the range, a
5
+ * `formula`, or the range's own `min`/`max` (which carry no value).
6
+ */
7
+ export interface CfValueObject {
8
+ type: 'num' | 'percent' | 'max' | 'min' | 'percentile' | 'formula';
9
+ value?: number | string;
10
+ }
11
+ /**
12
+ * A single conditional-formatting rule. `type` is the OOXML cfRule type; the remaining fields carry
13
+ * the operands that type needs and are absent otherwise. A rule the library does not model in depth
14
+ * still preserves `type`, `priority`, `operator`, `formulae`, and `dxfId` across a round-trip.
15
+ */
16
+ export interface ConditionalFormattingRule {
17
+ type: string;
18
+ /** Evaluation precedence; lower wins. Excel requires one — the writer supplies it when absent. */
19
+ priority?: number;
20
+ /** Halt evaluation of lower-priority rules on any cell this rule matches. */
21
+ stopIfTrue?: boolean;
22
+ /** cellIs / text comparison operator (`greaterThan`, `between`, `beginsWith`, …). */
23
+ operator?: string;
24
+ /** Formula operands: cellIs bounds, an expression predicate, a containsText target formula, … */
25
+ formulae?: (string | number)[];
26
+ /** The literal a containsText / beginsWith / endsWith rule searches for. */
27
+ text?: string;
28
+ /** A differential style authored inline, serialised into `<dxfs>` and referenced by the cfRule. */
29
+ style?: DifferentialStyle;
30
+ /** A differential-style reference by `<dxfs>` index, as read from a file (kept verbatim). */
31
+ dxfId?: string;
32
+ /** colorScale / dataBar / iconSet scale anchors, in order. */
33
+ cfvo?: CfValueObject[];
34
+ /** A dataBar's bar colour. */
35
+ color?: Color;
36
+ /** A colorScale's colours, one per {@link cfvo}. */
37
+ colors?: Color[];
38
+ /** A dataBar's gradient-fill flag. Lives only in the x14 extension, not the classic element. */
39
+ gradient?: boolean;
40
+ /** A dataBar's fill colour for negative values. An x14 extension property. */
41
+ negativeFillColor?: Color;
42
+ /** A dataBar's axis colour (the zero line between positive and negative bars). An x14 property. */
43
+ axisColor?: Color;
44
+ /** An iconSet's named icon family (e.g. `3TrafficLights1`). */
45
+ iconSet?: string;
46
+ /** top10 rank cutoff. */
47
+ rank?: number;
48
+ /** top10: the rank is a percentage rather than a count. */
49
+ percent?: boolean;
50
+ /** top10: rank from the bottom rather than the top. */
51
+ bottom?: boolean;
52
+ /** aboveAverage: match above (default) or below the average. */
53
+ aboveAverage?: boolean;
54
+ /** aboveAverage: include cells equal to the average. */
55
+ equalAverage?: boolean;
56
+ /** aboveAverage: match beyond this many standard deviations. */
57
+ stdDev?: number;
58
+ /** timePeriod window (`today`, `lastWeek`, …). */
59
+ timePeriod?: string;
60
+ }
61
+ /** A set of rules bound to the range(s) they cover. `ref` is an OOXML `sqref` — one or more
62
+ * space-separated areas (`"A1:C1 A3:C3 A5:C5"`), the shape Excel writes when one rule is applied to
63
+ * several non-contiguous selections at once. */
64
+ export interface ConditionalFormatting {
65
+ ref: string;
66
+ rules: ConditionalFormattingRule[];
67
+ }
68
+ /** A defensive deep copy, so a stored conditional formatting never aliases the caller's object nor
69
+ * any of its nested arrays (rules, formulae, cfvo, colours) or the differential style. */
70
+ export declare function cloneConditionalFormatting(cf: ConditionalFormatting): ConditionalFormatting;
@@ -0,0 +1,35 @@
1
+ // Conditional formatting — the rules that restyle a cell based on its value (a data bar, a colour
2
+ // scale, a "highlight cells greater than 10", a formula-driven expression). Like a data validation,
3
+ // it is a worksheet-level overlay keyed by a target range, not a facet owned by one cell: one rule
4
+ // covers a whole range, and several rules can layer on the same cells with an evaluation precedence.
5
+ //
6
+ // The model carries the operands each rule type needs and otherwise leaves them absent. A rule type
7
+ // the library does not interpret in depth still round-trips its `type`, `priority`, `operator`,
8
+ // `formulae`, and differential-style reference — so a read/write cycle never silently drops a rule.
9
+ /** A defensive deep copy, so a stored conditional formatting never aliases the caller's object nor
10
+ * any of its nested arrays (rules, formulae, cfvo, colours) or the differential style. */
11
+ export function cloneConditionalFormatting(cf) {
12
+ return { ref: cf.ref, rules: cf.rules.map(cloneRule) };
13
+ }
14
+ function cloneRule(rule) {
15
+ return {
16
+ ...rule,
17
+ ...(rule.formulae !== undefined ? { formulae: [...rule.formulae] } : {}),
18
+ ...(rule.cfvo !== undefined ? { cfvo: rule.cfvo.map((v) => ({ ...v })) } : {}),
19
+ ...(rule.color !== undefined ? { color: { ...rule.color } } : {}),
20
+ ...(rule.negativeFillColor !== undefined
21
+ ? { negativeFillColor: { ...rule.negativeFillColor } }
22
+ : {}),
23
+ ...(rule.axisColor !== undefined ? { axisColor: { ...rule.axisColor } } : {}),
24
+ ...(rule.colors !== undefined ? { colors: rule.colors.map((c) => ({ ...c })) } : {}),
25
+ ...(rule.style !== undefined ? { style: cloneStyle(rule.style) } : {}),
26
+ };
27
+ }
28
+ function cloneStyle(style) {
29
+ return {
30
+ ...style,
31
+ ...(style.font !== undefined ? { font: { ...style.font } } : {}),
32
+ ...(style.fill !== undefined ? { fill: { ...style.fill } } : {}),
33
+ ...(style.border !== undefined ? { border: { ...style.border } } : {}),
34
+ };
35
+ }
@@ -0,0 +1,2 @@
1
+ export declare function overwrite<T extends object>(target: T, source: T): void;
2
+ export declare function replaceContents<T>(array: T[], next: readonly T[]): void;
@@ -0,0 +1,25 @@
1
+ // Identity-preserving replacement of a container's contents.
2
+ //
3
+ // Several model fields are `readonly` fields holding a mutable object or array: a caller (or a live
4
+ // getter) may hold a durable reference to the container, so importing a model must replace what the
5
+ // container *holds* without swapping the container itself. These two primitives do that — one for an
6
+ // object's keys, one for an array's elements — so the field's identity survives a wholesale reload.
7
+ // Replace an object's contents in place: clear every own key, then copy `source`'s keys over. Used for
8
+ // fields like a worksheet's `properties`/`pageSetup`/`headerFooter`, which are `readonly` fields holding
9
+ // mutable objects, so importing a model must overwrite them rather than reassign — and clear any stale
10
+ // key the incoming model does not carry. `Reflect` deletes each existing key without pretending the
11
+ // object carries a string index signature.
12
+ export function overwrite(target, source) {
13
+ for (const key of Reflect.ownKeys(target))
14
+ Reflect.deleteProperty(target, key);
15
+ Object.assign(target, source);
16
+ }
17
+ // Replace an array's contents in place: clear it, then append `next` element by element. Appends
18
+ // individually rather than spreading `next` into a single `push(...next)` call, whose argument count the
19
+ // JS engine bounds by its call-stack limit — these arrays are filled from parsed files, so a hostile
20
+ // input must not be able to overflow that limit.
21
+ export function replaceContents(array, next) {
22
+ array.length = 0;
23
+ for (const item of next)
24
+ array.push(item);
25
+ }
@@ -0,0 +1,28 @@
1
+ import { type DataValidation, type DataValidationEntry } from './data-validation.ts';
2
+ export declare class DataValidationOverlay {
3
+ #private;
4
+ /**
5
+ * Attach a rule to a target range (`"B2:B20"`, a whole column `"B2:B1048576"`, or a space-separated
6
+ * `sqref` of several ranges). The rule is stored once against the range, not copied per covered
7
+ * cell, so a whole-column dropdown stays a single entry. A cell inside the range reports the rule
8
+ * through {@link at}.
9
+ *
10
+ * Pass `{extended: true}` to mark a rule that belongs in the 2009 extension form
11
+ * (`<x14:dataValidation>`) — the carrier Excel uses for a list source on another sheet and other
12
+ * shapes the standard element cannot express. The reader sets it for a rule found in that form so a
13
+ * round-trip writes it back there instead of silently corrupting the cross-sheet reference.
14
+ */
15
+ add(sqref: string, rule: DataValidation, options?: {
16
+ extended?: boolean;
17
+ }): void;
18
+ /** The data validations on this sheet, each bound to its target range, in insertion order. */
19
+ get entries(): readonly DataValidationEntry[];
20
+ /**
21
+ * The validation covering the 1-based `col`/`row`, or `undefined` when none does. The first added
22
+ * rule whose range contains the cell wins, mirroring how a spreadsheet resolves overlapping
23
+ * validations.
24
+ */
25
+ at(col: number, row: number): DataValidation | undefined;
26
+ /** Drop every validation, leaving the overlay empty. */
27
+ clear(): void;
28
+ }
@@ -0,0 +1,56 @@
1
+ // The data-validation overlay a Worksheet owns: an insertion-ordered list of range-bound rules, plus
2
+ // the decoded rectangles a point-in-rect lookup ({@link DataValidationOverlay.at}) tests against. Kept
3
+ // as its own class — the sibling to {@link GridEdits} that owns splice arithmetic — rather than inline
4
+ // on Worksheet, since a validation's storage (a rule plus its decoded ranges) is a self-contained unit
5
+ // Worksheet only ever adds to, reads, or clears wholesale.
6
+ import { cloneDataValidation, } from "./data-validation.js";
7
+ import { decodeSqrefRects } from "./merge.js";
8
+ export class DataValidationOverlay {
9
+ #entries = [];
10
+ #rects = [];
11
+ /**
12
+ * Attach a rule to a target range (`"B2:B20"`, a whole column `"B2:B1048576"`, or a space-separated
13
+ * `sqref` of several ranges). The rule is stored once against the range, not copied per covered
14
+ * cell, so a whole-column dropdown stays a single entry. A cell inside the range reports the rule
15
+ * through {@link at}.
16
+ *
17
+ * Pass `{extended: true}` to mark a rule that belongs in the 2009 extension form
18
+ * (`<x14:dataValidation>`) — the carrier Excel uses for a list source on another sheet and other
19
+ * shapes the standard element cannot express. The reader sets it for a rule found in that form so a
20
+ * round-trip writes it back there instead of silently corrupting the cross-sheet reference.
21
+ */
22
+ add(sqref, rule, options = {}) {
23
+ // One defensive copy, shared by the serialisable entry and the lookup index, so the getter never
24
+ // hands back a reference into the caller's object.
25
+ const stored = cloneDataValidation(rule);
26
+ const entry = { sqref, rule: stored };
27
+ if (options.extended)
28
+ entry.extended = true;
29
+ this.#entries.push(entry);
30
+ this.#rects.push({ rects: decodeSqrefRects(sqref), rule: stored });
31
+ }
32
+ /** The data validations on this sheet, each bound to its target range, in insertion order. */
33
+ get entries() {
34
+ return this.#entries;
35
+ }
36
+ /**
37
+ * The validation covering the 1-based `col`/`row`, or `undefined` when none does. The first added
38
+ * rule whose range contains the cell wins, mirroring how a spreadsheet resolves overlapping
39
+ * validations.
40
+ */
41
+ at(col, row) {
42
+ for (const { rects, rule } of this.#rects) {
43
+ for (const rect of rects) {
44
+ if (col >= rect.left && col <= rect.right && row >= rect.top && row <= rect.bottom) {
45
+ return rule;
46
+ }
47
+ }
48
+ }
49
+ return undefined;
50
+ }
51
+ /** Drop every validation, leaving the overlay empty. */
52
+ clear() {
53
+ this.#entries.length = 0;
54
+ this.#rects.length = 0;
55
+ }
56
+ }
@@ -0,0 +1,38 @@
1
+ /** The kind of constraint a validation enforces. `list` is a dropdown; `custom` is an arbitrary
2
+ * boolean formula; the rest bound a typed value (`whole`/`decimal`/`date`/`time`/`textLength`). */
3
+ export type DataValidationType = 'list' | 'whole' | 'decimal' | 'date' | 'time' | 'textLength' | 'custom';
4
+ /** How a typed validation compares its operand(s). Absent on a `list`/`custom` rule; defaults to
5
+ * `between` on a typed rule (the value Excel omits from the XML). */
6
+ export type DataValidationOperator = 'between' | 'notBetween' | 'equal' | 'notEqual' | 'greaterThan' | 'lessThan' | 'greaterThanOrEqual' | 'lessThanOrEqual';
7
+ /** How Excel reacts to input that fails the rule. */
8
+ export type DataValidationErrorStyle = 'stop' | 'warning' | 'information';
9
+ /** One validation rule. `formulae` holds the operand(s) — `formula1` then optional `formula2`: a
10
+ * numeric literal is stored as a number, while a cell reference, defined name, or list source keeps
11
+ * its verbatim string. */
12
+ export interface DataValidation {
13
+ type: DataValidationType;
14
+ operator?: DataValidationOperator;
15
+ formulae?: (string | number)[];
16
+ allowBlank?: boolean;
17
+ showInputMessage?: boolean;
18
+ showErrorMessage?: boolean;
19
+ errorStyle?: DataValidationErrorStyle;
20
+ error?: string;
21
+ errorTitle?: string;
22
+ prompt?: string;
23
+ promptTitle?: string;
24
+ }
25
+ /** A validation bound to the range(s) it covers. `sqref` is an OOXML `sqref` — one or more
26
+ * space-separated ranges. `extended` marks a rule stored in the 2009 extension form
27
+ * (`<x14:dataValidation>` inside the worksheet `<extLst>`) — Excel's carrier for validations a
28
+ * legacy `<dataValidation>` cannot express, such as a list source on another sheet. The flag is how
29
+ * a rule read from that form remembers to be written back to it, rather than downgraded to the
30
+ * standard element (which would corrupt a cross-sheet reference). */
31
+ export interface DataValidationEntry {
32
+ sqref: string;
33
+ rule: DataValidation;
34
+ extended?: boolean;
35
+ }
36
+ /** A defensive copy of a rule, so a stored validation never aliases the caller's object (nor its
37
+ * `formulae` array). */
38
+ export declare function cloneDataValidation(rule: DataValidation): DataValidation;