@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,249 @@
1
+ // A single cell: a typed value at a fixed 1-based `(row, col)` position.
2
+ //
3
+ // Position is immutable and numeric — the corpus locks `col`/`row` as 1-based
4
+ // *numbers* (legacy shipped a type declaration calling them strings, which broke
5
+ // strict consumers). The value is the only mutable state here; assigning it routes
6
+ // through the value model so the cell's `type` is always consistent with what it holds.
7
+ import { encodeAddress } from "./address.js";
8
+ import { NAMED_STYLE_ID } from "./internal.js";
9
+ import { assignStyleFacets, } from "./style.js";
10
+ import { coerceCellValue, detectValueType, } from "./value.js";
11
+ /**
12
+ * A single cell owns its value and every style facet outright. Each facet below — fill, number format,
13
+ * font, border, alignment, protection, quote-prefix, and note — is held in the cell's own field and
14
+ * *replaced* (never mutated in place) by its setter, so a facet set on one cell never aliases or bleeds
15
+ * onto its row, column, or sheet siblings. Each facet's own doc covers only what is specific to it.
16
+ */
17
+ export class Cell {
18
+ /** 1-based row index. */
19
+ row;
20
+ /** 1-based column index. */
21
+ col;
22
+ #value = null;
23
+ #fill;
24
+ #numFmt;
25
+ #font;
26
+ #border;
27
+ #alignment;
28
+ #protection;
29
+ #quotePrefix;
30
+ #namedStyleId;
31
+ #note;
32
+ constructor(row, col) {
33
+ if (!Number.isInteger(row) || row < 1) {
34
+ throw new RangeError(`cell row ${row} is out of bounds — rows start at 1`);
35
+ }
36
+ if (!Number.isInteger(col) || col < 1) {
37
+ throw new RangeError(`cell column ${col} is out of bounds — columns start at 1`);
38
+ }
39
+ this.row = row;
40
+ this.col = col;
41
+ }
42
+ /** Canonical A1 address of this cell (`"B3"`). */
43
+ get address() {
44
+ return encodeAddress(this.col, this.row);
45
+ }
46
+ /** The cell's value; `null` when empty. Assigning `undefined` clears it. */
47
+ get value() {
48
+ return this.#value;
49
+ }
50
+ set value(value) {
51
+ this.#value = coerceCellValue(value);
52
+ }
53
+ /** The observable {@link ValueType} of the current value. */
54
+ get type() {
55
+ return detectValueType(this.#value);
56
+ }
57
+ /**
58
+ * Assign rich text whose runs **inherit this cell's font**, so a run needs to state only what it
59
+ * changes: `setRichText([{text: 'Note:', font: {bold: true}}, {text: ' the rest'}])` keeps the
60
+ * cell's typeface, size and colour throughout and bolds the first run.
61
+ *
62
+ * This exists because a run's format element does **not** inherit anything. A `<rPr>` is a
63
+ * *complete* character format, and any facet it omits falls back to the workbook default font
64
+ * ({@link Workbook.defaultFont}) — not to the cell's. Verified against Excel: a cell set to
65
+ * Courier New 16 whose first run carries only `<b/>` renders that run in the workbook default face
66
+ * at the default size, bold, while the rest of the cell renders Courier New 16. So a run authored
67
+ * as `{bold: true}` beside a styled cell silently loses the face, which is the format's rule rather
68
+ * than a bug — and the reason this is a helper rather than a change to how runs are written.
69
+ *
70
+ * Composition is per facet: a facet the run names wins, one it omits comes from the cell. Assigning
71
+ * `value` directly stays the bare path, for a caller who wants a run that deliberately falls back
72
+ * to the workbook default.
73
+ *
74
+ * A cell that names no font of its own needs no composition — an omitted facet already falls back
75
+ * to the workbook default, which is exactly what such a cell renders in — so the runs pass through
76
+ * unchanged.
77
+ */
78
+ setRichText(runs) {
79
+ const base = this.#font;
80
+ this.#value =
81
+ base === undefined
82
+ ? { richText: [...runs] }
83
+ : { richText: runs.map((run) => ({ ...run, font: { ...base, ...run.font } })) };
84
+ }
85
+ /**
86
+ * The cell's full style — fill, number format, font, border, alignment, and protection — as one
87
+ * {@link CellStyle}, for restyling a cell wholesale without importing {@link applyCellStyle}
88
+ * separately (mirrors {@link Worksheet.model}'s getter/setter pair for the whole sheet). The
89
+ * getter carries only the facets this cell has set (the same shape {@link cellToModel} emits);
90
+ * the setter lays each facet `style` carries onto this cell — like every per-facet setter, it
91
+ * replaces that facet outright but leaves a facet `style` omits untouched, so `cell.style = {...}`
92
+ * composes with prior per-facet sets rather than clearing them wholesale.
93
+ */
94
+ get style() {
95
+ const style = {};
96
+ assignStyleFacets(style, this);
97
+ return style;
98
+ }
99
+ set style(style) {
100
+ applyCellStyle(this, style);
101
+ }
102
+ /** The cell's background fill, or `undefined` when it has none. */
103
+ get fill() {
104
+ return this.#fill;
105
+ }
106
+ set fill(fill) {
107
+ this.#fill = fill;
108
+ }
109
+ /**
110
+ * The cell's number-format code (`"0.00%"`, a custom accounting format, …), or
111
+ * `undefined` for the General format. Stored verbatim: the invariant form Excel
112
+ * persists — `.` decimal, `,` grouping, `/` date separator — is neither localized
113
+ * nor rewritten, so the code round-trips character-for-character. A cell that also carries
114
+ * a column-level format keeps both, so overriding one facet never drops the other.
115
+ */
116
+ get numFmt() {
117
+ return this.#numFmt;
118
+ }
119
+ set numFmt(numFmt) {
120
+ this.#numFmt = numFmt;
121
+ }
122
+ /**
123
+ * The cell's font — bold/italic/underline, size, colour, typeface — as a partial set
124
+ * of the facets that differ from the default (only the facets actually set are carried,
125
+ * exactly as OOXML stores them). `undefined` means the cell uses the workbook default font.
126
+ */
127
+ get font() {
128
+ return this.#font;
129
+ }
130
+ set font(font) {
131
+ this.#font = font;
132
+ }
133
+ /**
134
+ * The cell's border — the line style and colour of each side — or `undefined` when the
135
+ * cell has none. An absent edge within a border means that side is unbordered, so reading
136
+ * a cell never fabricates a border it does not have.
137
+ */
138
+ get border() {
139
+ return this.#border;
140
+ }
141
+ set border(border) {
142
+ this.#border = border;
143
+ }
144
+ /**
145
+ * The cell's alignment — how its content sits within the cell, plus the wrap/shrink flags —
146
+ * or `undefined` when it uses the defaults. The boolean flags are off unless explicitly set,
147
+ * so a cell that never enabled wrapping never reads back wrapped.
148
+ */
149
+ get alignment() {
150
+ return this.#alignment;
151
+ }
152
+ set alignment(alignment) {
153
+ this.#alignment = alignment;
154
+ }
155
+ /**
156
+ * The cell's protection — its locked/hidden flags, enforced only once the sheet is protected —
157
+ * or `undefined` when the cell carries neither. `locked` defaults to on in OOXML, so a cell
158
+ * that never touched protection is implicitly locked and reads back as `undefined`, not as
159
+ * `{locked: true}`; the flag only becomes explicit when a cell is unlocked.
160
+ */
161
+ get protection() {
162
+ return this.#protection;
163
+ }
164
+ set protection(protection) {
165
+ this.#protection = protection;
166
+ }
167
+ /**
168
+ * The quote-prefix flag: when set, a spreadsheet stores the cell's content as literal text even
169
+ * when it looks like a formula or number, and shows a leading apostrophe in the formula bar without
170
+ * that apostrophe being part of the stored value. `undefined` (or `false`) when unset. It is a
171
+ * cell-format flag — an attribute on the cell's `xf` record — so it composes independently of the
172
+ * value.
173
+ */
174
+ get quotePrefix() {
175
+ return this.#quotePrefix;
176
+ }
177
+ set quotePrefix(quotePrefix) {
178
+ this.#quotePrefix = quotePrefix;
179
+ }
180
+ /**
181
+ * The index of the named cell style this cell links to (its OOXML `xfId`), or `undefined` when the
182
+ * cell references no named style beyond the default. The cell inherits any facet its own direct
183
+ * format leaves unset from that named style; the reader resolves the effective look onto the cell's
184
+ * own facets, and this link is preserved so a round-trip keeps the cell tied to its named style
185
+ * rather than flattening it away.
186
+ *
187
+ * Codec-only: an index is meaningful only against the workbook's `namedStyles` table as the reader
188
+ * built it, and a value naming no entry leaves the cell pointing at nothing. Authors set the facets
189
+ * they want directly.
190
+ */
191
+ get [NAMED_STYLE_ID]() {
192
+ return this.#namedStyleId;
193
+ }
194
+ set [NAMED_STYLE_ID](namedStyleId) {
195
+ this.#namedStyleId = namedStyleId;
196
+ }
197
+ /**
198
+ * The cell's note (comment) as plain text, or `undefined` when it carries none. A note is
199
+ * metadata anchored to the cell, independent of its value: a cell can hold a note while empty,
200
+ * and clearing the value leaves the note intact. A structural edit that shifts the cell carries the
201
+ * note along to its new position.
202
+ */
203
+ get note() {
204
+ return this.#note;
205
+ }
206
+ set note(note) {
207
+ this.#note = note;
208
+ }
209
+ }
210
+ // Lay each present style facet of `style` onto `cell`, leaving facets it omits untouched. A {@link Cell}
211
+ // exposes every facet as a setter of its declared type, so it *is* a mutable {@link CellStyle} target and
212
+ // the shared {@link assignStyleFacets} loop drives it — the facet list lives only on {@link CellStyle}, and
213
+ // no apply path can forget a facet without a compile error there. Facet objects are assigned by reference,
214
+ // safe under the copy-on-write style model (setters replace, never mutate in place). This is the named
215
+ // entry point for the many paths that style a cell — a table column's format, a resolved read xf, a model
216
+ // assignment — so their call sites read as intent, not as a raw record copy.
217
+ export function applyCellStyle(cell, style) {
218
+ assignStyleFacets(cell, style);
219
+ }
220
+ // Copy a cell's value and every style facet onto a target cell. The source is a {@link CellModel},
221
+ // which a live {@link Cell} structurally satisfies, so this one primitive serves both directions that
222
+ // load content into a cell: a structural edit shifting a cell to fresh coordinates (`Cell` fixes its
223
+ // `(row, col)` at construction, so the shifted cell is a new cell carrying the original's content) and
224
+ // assigning a {@link WorksheetModel} onto a sheet. Position is never copied — the target keeps its own
225
+ // `(row, col)`. The style facets go through {@link applyCellStyle} (targets are always fresh cells, so
226
+ // its skip-if-absent is equivalent to a full copy here). Paired with {@link cellToModel} (the read
227
+ // direction); a facet cellToModel emits but applyCellStyle omits (or the reverse) would silently drop
228
+ // on a model round-trip — the historical merge-loss failure the CellStyle tuple now guards by type.
229
+ export function copyCellContent(source, target) {
230
+ target.value = source.value;
231
+ applyCellStyle(target, source);
232
+ target.note = source.note;
233
+ }
234
+ // Snapshot a cell's position and content as a {@link CellModel} — the read direction paired with
235
+ // {@link copyCellContent}'s write. The style facets flow through the same {@link assignStyleFacets} loop
236
+ // as every other copy (a {@link Cell} is structurally a {@link CellStyle} source), so this direction emits
237
+ // exactly the facets the apply direction consumes: a `dst.model = src.model` round-trip carries every one,
238
+ // and a facet added to {@link CellStyle} propagates here without a hand edit. Facets the cell does not
239
+ // carry are left off the model rather than pinned to `undefined`; no consumer distinguishes the two.
240
+ export function cellToModel(cell) {
241
+ const model = {
242
+ row: cell.row,
243
+ col: cell.col,
244
+ value: cell.value,
245
+ note: cell.note,
246
+ };
247
+ assignStyleFacets(model, cell);
248
+ return model;
249
+ }
@@ -0,0 +1,58 @@
1
+ import type { Color } from './style.ts';
2
+ import { type ThemeColorScheme } from './theme.ts';
3
+ /**
4
+ * The built-in indexed colour palette (ECMA-376 §18.8.27), by index. Entries 0–7 duplicate 8–15 —
5
+ * redundancy the spec preserves for backwards compatibility with the legacy formats this palette came
6
+ * from — and the table is only 64 long: indices 64 and 65 are the *system* foreground and background,
7
+ * which name whatever the operating system's window colours are and therefore have no fixed value at
8
+ * all (see {@link SYSTEM_INDEXED_COLORS}).
9
+ *
10
+ * The spec writes each entry with a leading `00`. That byte is not an alpha channel — a palette of
11
+ * fully transparent colours would be absurd — it is an artefact of the 32-bit colour records these
12
+ * values were lifted from, which is why {@link resolveColor} returns them fully opaque.
13
+ */
14
+ export declare const DEFAULT_INDEXED_COLORS: readonly string[];
15
+ /**
16
+ * The two indices that are not colours: 64 is the system foreground and 65 the system background.
17
+ * They resolve to whatever the viewing system's window colours are, so this library reports them
18
+ * unresolved rather than inventing black and white — a caller that wants to paint them must decide
19
+ * for itself what "automatic" means in its context. `indexed="64"` in particular is extremely common:
20
+ * it is the placeholder every solid fill Excel writes carries as its background colour.
21
+ */
22
+ export declare const SYSTEM_INDEXED_COLORS: ReadonlySet<number>;
23
+ /** What a colour reference is resolved against: the workbook's theme scheme and indexed palette. */
24
+ export interface ColorResolutionContext {
25
+ /** The theme's colour scheme; the Office default when the workbook carries no theme of its own. */
26
+ readonly theme?: ThemeColorScheme | undefined;
27
+ /**
28
+ * The workbook's custom indexed palette, by index, each entry an ARGB string. Empty or absent means
29
+ * the workbook rides {@link DEFAULT_INDEXED_COLORS}. A custom palette replaces the built-in one
30
+ * wholesale — that is what `<indexedColors>` means — so a short custom palette leaves the indices
31
+ * past its end unresolved rather than falling through to the built-in entry.
32
+ */
33
+ readonly indexed?: readonly string[] | undefined;
34
+ }
35
+ /**
36
+ * Resolve a colour reference to a concrete 8-hex ARGB string, or `undefined` when it cannot be
37
+ * resolved — an `auto` colour, a system indexed colour, a theme slot the workbook's scheme does not
38
+ * declare, or an index past the end of a custom palette.
39
+ *
40
+ * Precedence follows what the encodings mean: an explicit `argb` is already concrete and wins; then
41
+ * `theme`, then `indexed`. A `tint` applies to whatever the base resolved to.
42
+ *
43
+ * Alpha: a resolved `theme`/`indexed` colour comes back fully opaque, because neither the theme scheme
44
+ * nor the palette carries a meaningful alpha (see {@link DEFAULT_INDEXED_COLORS}). An explicit `argb`
45
+ * keeps the alpha the file stated.
46
+ */
47
+ export declare function resolveColor(color: Color, context?: ColorResolutionContext): string | undefined;
48
+ /**
49
+ * Apply a `tint` to a concrete ARGB colour: convert to HLS, shift the luminance, convert back.
50
+ *
51
+ * The shift is ECMA-376's (§18.3.1.15): `-1` darkens to black, `+1` lightens to white, `0` is a
52
+ * no-op. The spec states the formula but not the rounding either side of the RGB↔HLS conversion, so
53
+ * an implementation cannot be checked against the prose alone. This one lands within 2/255 per
54
+ * channel of what Excel Desktop itself renders, measured over three hues × twelve tints and recorded
55
+ * in `test/corpus/fixtures/excel-oracle/theme-color-tint-luminance.json`. Closing that last gap would
56
+ * mean reproducing one Excel build's fixed-point HLS rounding, and the difference is not visible.
57
+ */
58
+ export declare function applyTint(argb: string, tint: number): string;
@@ -0,0 +1,170 @@
1
+ // Turning an OOXML colour *reference* into a concrete colour.
2
+ //
3
+ // A `<color>` in a spreadsheet states its value in one of three ways, and two of them are references
4
+ // that mean nothing on their own: `indexed="n"` points into a 64-entry legacy palette carried over
5
+ // from the BIFF era, and `theme="n"` points into the workbook theme's colour scheme. Either may
6
+ // additionally carry a `tint`, which lightens or darkens whatever it resolved to.
7
+ //
8
+ // Resolution is deliberately a *derived* view. Nothing here writes back into the model: a `Color`
9
+ // keeps the encoding its file used, so a round-trip re-emits `theme="4" tint="0.4"` rather than
10
+ // rewriting every cell as a literal ARGB — which would bloat the styles table, break the link to the
11
+ // theme (recolouring the workbook would stop working), and change what the file means.
12
+ import { DEFAULT_THEME_COLOR_SCHEME, THEME_COLOR_SLOTS } from "./theme.js";
13
+ /**
14
+ * The built-in indexed colour palette (ECMA-376 §18.8.27), by index. Entries 0–7 duplicate 8–15 —
15
+ * redundancy the spec preserves for backwards compatibility with the legacy formats this palette came
16
+ * from — and the table is only 64 long: indices 64 and 65 are the *system* foreground and background,
17
+ * which name whatever the operating system's window colours are and therefore have no fixed value at
18
+ * all (see {@link SYSTEM_INDEXED_COLORS}).
19
+ *
20
+ * The spec writes each entry with a leading `00`. That byte is not an alpha channel — a palette of
21
+ * fully transparent colours would be absurd — it is an artefact of the 32-bit colour records these
22
+ * values were lifted from, which is why {@link resolveColor} returns them fully opaque.
23
+ */
24
+ // biome-ignore format: laid out eight per row, matching how the spec tabulates the palette —
25
+ // the 0-7 / 8-15 duplication and the 16-entry banding are legible here and invisible one-per-line.
26
+ export const DEFAULT_INDEXED_COLORS = [
27
+ '00000000', '00FFFFFF', '00FF0000', '0000FF00', '000000FF', '00FFFF00', '00FF00FF', '0000FFFF',
28
+ '00000000', '00FFFFFF', '00FF0000', '0000FF00', '000000FF', '00FFFF00', '00FF00FF', '0000FFFF',
29
+ '00800000', '00008000', '00000080', '00808000', '00800080', '00008080', '00C0C0C0', '00808080',
30
+ '009999FF', '00993366', '00FFFFCC', '00CCFFFF', '00660066', '00FF8080', '000066CC', '00CCCCFF',
31
+ '00000080', '00FF00FF', '00FFFF00', '0000FFFF', '00800080', '00800000', '00008080', '000000FF',
32
+ '0000CCFF', '00CCFFFF', '00CCFFCC', '00FFFF99', '0099CCFF', '00FF99CC', '00CC99FF', '00FFCC99',
33
+ '003366FF', '0033CCCC', '0099CC00', '00FFCC00', '00FF9900', '00FF6600', '00666699', '00969696',
34
+ '00003366', '00339966', '00003300', '00333300', '00993300', '00993366', '00333399', '00333333',
35
+ ];
36
+ /**
37
+ * The two indices that are not colours: 64 is the system foreground and 65 the system background.
38
+ * They resolve to whatever the viewing system's window colours are, so this library reports them
39
+ * unresolved rather than inventing black and white — a caller that wants to paint them must decide
40
+ * for itself what "automatic" means in its context. `indexed="64"` in particular is extremely common:
41
+ * it is the placeholder every solid fill Excel writes carries as its background colour.
42
+ */
43
+ export const SYSTEM_INDEXED_COLORS = new Set([64, 65]);
44
+ /**
45
+ * Resolve a colour reference to a concrete 8-hex ARGB string, or `undefined` when it cannot be
46
+ * resolved — an `auto` colour, a system indexed colour, a theme slot the workbook's scheme does not
47
+ * declare, or an index past the end of a custom palette.
48
+ *
49
+ * Precedence follows what the encodings mean: an explicit `argb` is already concrete and wins; then
50
+ * `theme`, then `indexed`. A `tint` applies to whatever the base resolved to.
51
+ *
52
+ * Alpha: a resolved `theme`/`indexed` colour comes back fully opaque, because neither the theme scheme
53
+ * nor the palette carries a meaningful alpha (see {@link DEFAULT_INDEXED_COLORS}). An explicit `argb`
54
+ * keeps the alpha the file stated.
55
+ */
56
+ export function resolveColor(color, context = {}) {
57
+ const base = resolveBase(color, context);
58
+ if (base === undefined)
59
+ return undefined;
60
+ const { tint } = color;
61
+ if (tint === undefined || !Number.isFinite(tint) || tint === 0)
62
+ return base;
63
+ return applyTint(base, tint);
64
+ }
65
+ function resolveBase(color, context) {
66
+ if (color.argb !== undefined)
67
+ return normalizeArgb(color.argb);
68
+ if (color.theme !== undefined) {
69
+ const slot = THEME_COLOR_SLOTS[color.theme];
70
+ if (slot === undefined)
71
+ return undefined;
72
+ const value = (context.theme ?? DEFAULT_THEME_COLOR_SCHEME)[slot];
73
+ return value === undefined ? undefined : `FF${value.toUpperCase()}`;
74
+ }
75
+ if (color.indexed !== undefined) {
76
+ if (SYSTEM_INDEXED_COLORS.has(color.indexed))
77
+ return undefined;
78
+ const custom = context.indexed;
79
+ const entry = custom !== undefined && custom.length > 0
80
+ ? custom[color.indexed]
81
+ : DEFAULT_INDEXED_COLORS[color.indexed];
82
+ if (entry === undefined)
83
+ return undefined;
84
+ const normalized = normalizeArgb(entry);
85
+ return normalized === undefined ? undefined : `FF${normalized.slice(2)}`;
86
+ }
87
+ return undefined;
88
+ }
89
+ // Accept the shapes a colour value legitimately arrives in — 6-hex RGB, 8-hex ARGB, either with a
90
+ // leading '#' — and reject anything else rather than returning a half-parsed value. This is a *read*
91
+ // path over foreign data, so a malformed entry resolves to nothing; the writer's own normaliser
92
+ // throws, because there the malformed value is a caller's bug.
93
+ function normalizeArgb(value) {
94
+ const hex = value.startsWith('#') ? value.slice(1) : value;
95
+ const argb = hex.length === 6 ? `FF${hex}` : hex;
96
+ return /^[0-9a-fA-F]{8}$/.test(argb) ? argb.toUpperCase() : undefined;
97
+ }
98
+ /**
99
+ * Apply a `tint` to a concrete ARGB colour: convert to HLS, shift the luminance, convert back.
100
+ *
101
+ * The shift is ECMA-376's (§18.3.1.15): `-1` darkens to black, `+1` lightens to white, `0` is a
102
+ * no-op. The spec states the formula but not the rounding either side of the RGB↔HLS conversion, so
103
+ * an implementation cannot be checked against the prose alone. This one lands within 2/255 per
104
+ * channel of what Excel Desktop itself renders, measured over three hues × twelve tints and recorded
105
+ * in `test/corpus/fixtures/excel-oracle/theme-color-tint-luminance.json`. Closing that last gap would
106
+ * mean reproducing one Excel build's fixed-point HLS rounding, and the difference is not visible.
107
+ */
108
+ export function applyTint(argb, tint) {
109
+ const alpha = argb.slice(0, 2);
110
+ const r = Number.parseInt(argb.slice(2, 4), 16) / 255;
111
+ const g = Number.parseInt(argb.slice(4, 6), 16) / 255;
112
+ const b = Number.parseInt(argb.slice(6, 8), 16) / 255;
113
+ const max = Math.max(r, g, b);
114
+ const min = Math.min(r, g, b);
115
+ const lightness = (max + min) / 2;
116
+ let saturation = 0;
117
+ let hue = 0;
118
+ if (max !== min) {
119
+ const delta = max - min;
120
+ saturation = lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min);
121
+ if (max === r)
122
+ hue = (g - b) / delta + (g < b ? 6 : 0);
123
+ else if (max === g)
124
+ hue = (b - r) / delta + 2;
125
+ else
126
+ hue = (r - g) / delta + 4;
127
+ hue /= 6;
128
+ }
129
+ // Lum' = Lum * (1 + tint) when darkening; Lum' = Lum * (1 - tint) + tint when lightening. The
130
+ // spec writes the second as `Lum * (1 - tint) + (HLSMAX - HLSMAX * (1 - tint))`, which is the same
131
+ // thing once the HLSMAX scale is divided out.
132
+ const shifted = tint < 0 ? lightness * (1 + tint) : lightness * (1 - tint) + tint;
133
+ return alpha + hlsToRgbHex(hue, saturation, clamp01(shifted));
134
+ }
135
+ function clamp01(value) {
136
+ return value < 0 ? 0 : value > 1 ? 1 : value;
137
+ }
138
+ function hlsToRgbHex(hue, saturation, lightness) {
139
+ if (saturation === 0) {
140
+ const grey = channelHex(lightness);
141
+ return grey + grey + grey;
142
+ }
143
+ const q = lightness < 0.5
144
+ ? lightness * (1 + saturation)
145
+ : lightness + saturation - lightness * saturation;
146
+ const p = 2 * lightness - q;
147
+ return (channelHex(hueToChannel(p, q, hue + 1 / 3)) +
148
+ channelHex(hueToChannel(p, q, hue)) +
149
+ channelHex(hueToChannel(p, q, hue - 1 / 3)));
150
+ }
151
+ function hueToChannel(p, q, offset) {
152
+ let t = offset;
153
+ if (t < 0)
154
+ t += 1;
155
+ if (t > 1)
156
+ t -= 1;
157
+ if (t < 1 / 6)
158
+ return p + (q - p) * 6 * t;
159
+ if (t < 1 / 2)
160
+ return q;
161
+ if (t < 2 / 3)
162
+ return p + (q - p) * (2 / 3 - t) * 6;
163
+ return p;
164
+ }
165
+ function channelHex(value) {
166
+ return Math.round(clamp01(value) * 255)
167
+ .toString(16)
168
+ .toUpperCase()
169
+ .padStart(2, '0');
170
+ }
@@ -0,0 +1,87 @@
1
+ import type { Cell } from './cell.ts';
2
+ import type { Alignment, Border, Fill, Font, Protection } from './style.ts';
3
+ import type { CellValue } from './value.ts';
4
+ import type { ColumnProperties, Worksheet } from './worksheet.ts';
5
+ export declare class Column {
6
+ #private;
7
+ /** 1-based column index. Fixed for this handle's lifetime. */
8
+ readonly index: number;
9
+ /** @throws {RangeError} if the index is not a positive integer. */
10
+ constructor(sheet: Worksheet, index: number);
11
+ /** The column's letters (`"B"`) — the spreadsheet-facing name for {@link index}. */
12
+ get letter(): string;
13
+ /**
14
+ * The column's format record if it has one, else `undefined` — a read that never fabricates, so a
15
+ * serializer can ask every column it visits whether there are attributes to emit without giving
16
+ * each one an empty record. Read-only on purpose: {@link width} and its siblings are how a column
17
+ * is formatted, and they create the record on first write.
18
+ */
19
+ get properties(): Readonly<ColumnProperties> | undefined;
20
+ /**
21
+ * Stable key naming this column so a keyed-object row (see {@link Worksheet.addRow}) can place a
22
+ * value under it by name rather than position. In-memory only — never serialized to OOXML.
23
+ */
24
+ get key(): string | undefined;
25
+ set key(key: string | undefined);
26
+ /** Column width in character units; `undefined` leaves the sheet default in force. */
27
+ get width(): number | undefined;
28
+ set width(width: number | undefined);
29
+ /** Whether the column is hidden. */
30
+ get hidden(): boolean | undefined;
31
+ set hidden(hidden: boolean | undefined);
32
+ /** Outline (grouping) depth; 0 or `undefined` means ungrouped. */
33
+ get outlineLevel(): number | undefined;
34
+ set outlineLevel(outlineLevel: number | undefined);
35
+ /** Whether this column is the collapsed summary of an outline group. */
36
+ get collapsed(): boolean | undefined;
37
+ set collapsed(collapsed: boolean | undefined);
38
+ /** Default fill for the column's cells that set none of their own. */
39
+ get fill(): Fill | undefined;
40
+ set fill(fill: Fill | undefined);
41
+ /** Default number format for the column's cells that set none of their own. */
42
+ get numFmt(): string | undefined;
43
+ set numFmt(numFmt: string | undefined);
44
+ /** Default font for the column's cells that set none of their own. */
45
+ get font(): Font | undefined;
46
+ set font(font: Font | undefined);
47
+ /** Default border for the column's cells that set none of their own. */
48
+ get border(): Border | undefined;
49
+ set border(border: Border | undefined);
50
+ /** Default alignment for the column's cells that set none of their own. */
51
+ get alignment(): Alignment | undefined;
52
+ set alignment(alignment: Alignment | undefined);
53
+ /** Default protection flags for the column's cells that set none of their own. */
54
+ get protection(): Protection | undefined;
55
+ set protection(protection: Protection | undefined);
56
+ /**
57
+ * The cell at a 1-based row number in this column, creating it on first access. Resolves through
58
+ * merges exactly as {@link Worksheet.getCell} does.
59
+ *
60
+ * @throws {RangeError} if the row is not a positive integer.
61
+ */
62
+ getCell(row: number): Cell;
63
+ /**
64
+ * The column's materialised cells in ascending row order. Sparse: a row that never wrote to this
65
+ * column has no cell here.
66
+ */
67
+ get cells(): readonly Cell[];
68
+ /**
69
+ * The column's values by position, index 0 being row 1. Sparse in the same way {@link cells} is:
70
+ * a row with no cell in this column is a hole, which is what distinguishes "never written" from a
71
+ * cell holding `null`.
72
+ *
73
+ * Assigning places each value it names and leaves every other row untouched, mirroring
74
+ * {@link Row.values} — a hole or an explicit `undefined` skips that row, and a shorter array does
75
+ * not clear the tail.
76
+ */
77
+ get values(): (CellValue | undefined)[];
78
+ set values(values: (CellValue | undefined)[]);
79
+ }
80
+ type AssertNever<T extends never> = T;
81
+ /**
82
+ * Compile-time proof that {@link Column} mirrors every {@link ColumnProperties} field — including
83
+ * the six inherited `CellStyle` facets, so a seventh facet reaches this handle the moment it joins
84
+ * the tuple. See the counterpart on `Row` for why the mirror needs proving rather than reviewing.
85
+ */
86
+ export type EveryColumnPropertyIsMirrored = AssertNever<Exclude<keyof ColumnProperties, keyof Column & keyof ColumnProperties>>;
87
+ export {};