@devmm/puredocs-excel 1.0.5 → 1.0.7

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.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { unzipSync, zipSync } from 'fflate';
1
+ import { unzipSync, zipSync, deflateSync, deflate } from 'fflate';
2
2
 
3
3
  var __typeError = (msg) => {
4
4
  throw TypeError(msg);
@@ -48,10 +48,10 @@ function parseMinimal(xmlString) {
48
48
  }
49
49
  function parseAttributes(raw) {
50
50
  const attrs = {};
51
- const re = /(\S+?)\s*=\s*["']([^"']*)["']/g;
51
+ const re = /([^\s=/>]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
52
52
  let m;
53
53
  while ((m = re.exec(raw)) !== null) {
54
- attrs[m[1]] = m[2].replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
54
+ attrs[m[1]] = decodeXmlEntities(m[2] ?? m[3] ?? "");
55
55
  }
56
56
  return attrs;
57
57
  }
@@ -105,7 +105,7 @@ function parseMinimal(xmlString) {
105
105
  } else {
106
106
  const textStart = pos;
107
107
  skipUntil("<");
108
- const text = xmlString.slice(textStart, pos).replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
108
+ const text = decodeXmlEntities(xmlString.slice(textStart, pos));
109
109
  if (stack.length > 0 && text.trim()) {
110
110
  stack[stack.length - 1].textContent += text;
111
111
  }
@@ -114,6 +114,10 @@ function parseMinimal(xmlString) {
114
114
  if (!root) throw new Error("XML parse error: empty document.");
115
115
  return root;
116
116
  }
117
+ function decodeXmlEntities(value) {
118
+ if (value.indexOf("&") === -1) return value;
119
+ return value.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
120
+ }
117
121
  function parseXml(xmlString) {
118
122
  if (typeof DOMParser !== "undefined") {
119
123
  return parseDom(xmlString);
@@ -190,6 +194,124 @@ function listEntries(entries, prefix) {
190
194
  }
191
195
  return results.sort();
192
196
  }
197
+
198
+ // src/io/zip-container.ts
199
+ var LOCAL_HEADER = 67324752;
200
+ var CENTRAL_HEADER = 33639248;
201
+ var END_OF_CENTRAL_DIR = 101010256;
202
+ var DOS_DATE = 33;
203
+ var DOS_TIME = 0;
204
+ var MAX_ENTRIES = 65535;
205
+ var MAX_BYTES = 4294967295;
206
+ var CRC_TABLE = (() => {
207
+ const table = new Int32Array(256);
208
+ for (let i = 0; i < 256; i++) {
209
+ let c = i;
210
+ for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
211
+ table[i] = c;
212
+ }
213
+ return table;
214
+ })();
215
+ function crc32(data) {
216
+ let c = -1;
217
+ for (let i = 0; i < data.length; i++) {
218
+ c = c >>> 8 ^ CRC_TABLE[(c ^ data[i]) & 255];
219
+ }
220
+ return (c ^ -1) >>> 0;
221
+ }
222
+ function exceedsZipLimits(parts) {
223
+ if (parts.length > MAX_ENTRIES) return true;
224
+ let total = 0;
225
+ for (const p of parts) {
226
+ total += 30 + utf8Length(p.name) * 2 + 46 + p.deflated.length;
227
+ if (total > MAX_BYTES || p.rawSize > MAX_BYTES) return true;
228
+ }
229
+ return false;
230
+ }
231
+ function buildZipContainer(parts) {
232
+ const names = parts.map((p) => encodeName(p.name));
233
+ let localSize = 0;
234
+ let centralSize = 0;
235
+ for (let i = 0; i < parts.length; i++) {
236
+ localSize += 30 + names[i].bytes.length + parts[i].deflated.length;
237
+ centralSize += 46 + names[i].bytes.length;
238
+ }
239
+ const out = new Uint8Array(localSize + centralSize + 22);
240
+ const view = new DataView(out.buffer);
241
+ const offsets = [];
242
+ let at = 0;
243
+ for (let i = 0; i < parts.length; i++) {
244
+ const part = parts[i];
245
+ const name = names[i];
246
+ offsets.push(at);
247
+ view.setUint32(at, LOCAL_HEADER, true);
248
+ view.setUint16(at + 4, 20, true);
249
+ view.setUint16(at + 6, name.utf8Flag, true);
250
+ view.setUint16(at + 8, 8, true);
251
+ view.setUint16(at + 10, DOS_TIME, true);
252
+ view.setUint16(at + 12, DOS_DATE, true);
253
+ view.setUint32(at + 14, part.crc, true);
254
+ view.setUint32(at + 18, part.deflated.length, true);
255
+ view.setUint32(at + 22, part.rawSize, true);
256
+ view.setUint16(at + 26, name.bytes.length, true);
257
+ view.setUint16(at + 28, 0, true);
258
+ at += 30;
259
+ out.set(name.bytes, at);
260
+ at += name.bytes.length;
261
+ out.set(part.deflated, at);
262
+ at += part.deflated.length;
263
+ }
264
+ const centralStart = at;
265
+ for (let i = 0; i < parts.length; i++) {
266
+ const part = parts[i];
267
+ const name = names[i];
268
+ view.setUint32(at, CENTRAL_HEADER, true);
269
+ view.setUint16(at + 4, 20, true);
270
+ view.setUint16(at + 6, 20, true);
271
+ view.setUint16(at + 8, name.utf8Flag, true);
272
+ view.setUint16(at + 10, 8, true);
273
+ view.setUint16(at + 12, DOS_TIME, true);
274
+ view.setUint16(at + 14, DOS_DATE, true);
275
+ view.setUint32(at + 16, part.crc, true);
276
+ view.setUint32(at + 20, part.deflated.length, true);
277
+ view.setUint32(at + 24, part.rawSize, true);
278
+ view.setUint16(at + 28, name.bytes.length, true);
279
+ view.setUint16(at + 30, 0, true);
280
+ view.setUint16(at + 32, 0, true);
281
+ view.setUint16(at + 34, 0, true);
282
+ view.setUint16(at + 36, 0, true);
283
+ view.setUint32(at + 38, 0, true);
284
+ view.setUint32(at + 42, offsets[i], true);
285
+ at += 46;
286
+ out.set(name.bytes, at);
287
+ at += name.bytes.length;
288
+ }
289
+ view.setUint32(at, END_OF_CENTRAL_DIR, true);
290
+ view.setUint16(at + 4, 0, true);
291
+ view.setUint16(at + 6, 0, true);
292
+ view.setUint16(at + 8, parts.length, true);
293
+ view.setUint16(at + 10, parts.length, true);
294
+ view.setUint32(at + 12, at - centralStart, true);
295
+ view.setUint32(at + 16, centralStart, true);
296
+ view.setUint16(at + 20, 0, true);
297
+ return out;
298
+ }
299
+ function encodeName(name) {
300
+ const bytes = new TextEncoder().encode(name);
301
+ let ascii = true;
302
+ for (let i = 0; i < bytes.length; i++) {
303
+ if (bytes[i] > 127) {
304
+ ascii = false;
305
+ break;
306
+ }
307
+ }
308
+ return { bytes, utf8Flag: ascii ? 0 : 2048 };
309
+ }
310
+ function utf8Length(name) {
311
+ return new TextEncoder().encode(name).length;
312
+ }
313
+
314
+ // src/io/zip-writer.ts
193
315
  function zipXlsx(entries) {
194
316
  const zippable = {};
195
317
  for (const [path, content] of entries) {
@@ -204,6 +326,95 @@ function zipXlsx(entries) {
204
326
  );
205
327
  }
206
328
  }
329
+ var ASYNC_MIN_BYTES = 512 * 1024;
330
+ function createDeflateCache() {
331
+ return /* @__PURE__ */ new Map();
332
+ }
333
+ function compress(name, source, bytes) {
334
+ return {
335
+ name,
336
+ source,
337
+ deflated: deflateSync(bytes, { level: 6 }),
338
+ crc: crc32(bytes),
339
+ rawSize: bytes.length
340
+ };
341
+ }
342
+ function zipXlsxCached(entries, cache) {
343
+ const parts = collectParts(entries, cache);
344
+ pruneCache(entries, cache);
345
+ return exceedsZipLimits(parts) ? zipXlsx(entries) : buildZipContainer(parts);
346
+ }
347
+ async function zipXlsxCachedAsync(entries, cache) {
348
+ const order = [];
349
+ const ready = /* @__PURE__ */ new Map();
350
+ const jobs = [];
351
+ const todo = [];
352
+ let todoBytes = 0;
353
+ for (const [path, content] of entries) {
354
+ order.push(path);
355
+ const cached = cache.get(path);
356
+ if (cached && cached.source === content) {
357
+ ready.set(path, cached);
358
+ continue;
359
+ }
360
+ const bytes = typeof content === "string" ? encodeUtf8(content) : content;
361
+ todo.push({ path, content, bytes });
362
+ todoBytes += bytes.length;
363
+ }
364
+ if (todoBytes <= ASYNC_MIN_BYTES) {
365
+ for (const { path, content, bytes } of todo) {
366
+ const compressed = compress(path, content, bytes);
367
+ cache.set(path, compressed);
368
+ ready.set(path, compressed);
369
+ }
370
+ } else {
371
+ for (const { path, content, bytes } of todo) {
372
+ jobs.push(deflateAsync(bytes).then((deflated) => ({
373
+ name: path,
374
+ source: content,
375
+ deflated,
376
+ crc: crc32(bytes),
377
+ rawSize: bytes.length
378
+ })));
379
+ }
380
+ }
381
+ for (const part of await Promise.all(jobs)) {
382
+ cache.set(part.name, part);
383
+ ready.set(part.name, part);
384
+ }
385
+ pruneCache(entries, cache);
386
+ const parts = order.map((path) => ready.get(path));
387
+ return exceedsZipLimits(parts) ? zipXlsx(entries) : buildZipContainer(parts);
388
+ }
389
+ function deflateAsync(bytes) {
390
+ return new Promise((resolve, reject) => {
391
+ deflate(bytes, { level: 6 }, (err, data) => {
392
+ if (err) reject(new Error(`Failed to create xlsx: ${err.message}`));
393
+ else resolve(data);
394
+ });
395
+ });
396
+ }
397
+ function collectParts(entries, cache) {
398
+ const parts = [];
399
+ for (const [path, content] of entries) {
400
+ const cached = cache.get(path);
401
+ if (cached && cached.source === content) {
402
+ parts.push(cached);
403
+ continue;
404
+ }
405
+ const bytes = typeof content === "string" ? encodeUtf8(content) : content;
406
+ const part = compress(path, content, bytes);
407
+ cache.set(path, part);
408
+ parts.push(part);
409
+ }
410
+ return parts;
411
+ }
412
+ function pruneCache(entries, cache) {
413
+ if (cache.size === entries.size) return;
414
+ for (const path of [...cache.keys()]) {
415
+ if (!entries.has(path)) cache.delete(path);
416
+ }
417
+ }
207
418
  function setXmlEntry(entries, path, xml2) {
208
419
  entries.set(path, xml2);
209
420
  }
@@ -294,7 +505,10 @@ var REL_TYPES = {
294
505
  styles: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
295
506
  chart: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart",
296
507
  drawing: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
297
- hyperlink: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
508
+ hyperlink: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
509
+ comments: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments",
510
+ vmlDrawing: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing",
511
+ calcChain: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/calcChain"
298
512
  };
299
513
  var CONTENT_TYPES = {
300
514
  workbook: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
@@ -304,7 +518,10 @@ var CONTENT_TYPES = {
304
518
  drawing: "application/vnd.openxmlformats-officedocument.drawing+xml",
305
519
  chart: "application/vnd.openxmlformats-officedocument.drawingml.chart+xml",
306
520
  relationships: "application/vnd.openxmlformats-package.relationships+xml",
307
- coreProperties: "application/vnd.openxmlformats-package.core-properties+xml"
521
+ coreProperties: "application/vnd.openxmlformats-package.core-properties+xml",
522
+ comments: "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml",
523
+ vmlDrawing: "application/vnd.openxmlformats-officedocument.vmlDrawing",
524
+ calcChain: "application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml"
308
525
  };
309
526
 
310
527
  // src/io/ooxml-templates.ts
@@ -337,18 +554,20 @@ var ROOT_RELS = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
337
554
  <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
338
555
  <Relationship Id="rId1" Type="${REL_TYPES.officeDocument}" Target="xl/workbook.xml"/>
339
556
  </Relationships>`;
340
- function buildWorkbookRels(sheetCount) {
557
+ function buildWorkbookRels(sheetCount, includeCalcChain = false) {
341
558
  const sheetRels = Array.from(
342
559
  { length: sheetCount },
343
560
  (_, i) => ` <Relationship Id="rId${i + 1}" Type="${REL_TYPES.worksheet}" Target="worksheets/sheet${i + 1}.xml"/>`
344
561
  ).join("\n");
345
562
  const stylesId = sheetCount + 1;
346
563
  const sharedId = sheetCount + 2;
564
+ const calcChainRel = includeCalcChain ? `
565
+ <Relationship Id="rId${sheetCount + 3}" Type="${REL_TYPES.calcChain}" Target="calcChain.xml"/>` : "";
347
566
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
348
567
  <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
349
568
  ${sheetRels}
350
569
  <Relationship Id="rId${stylesId}" Type="${REL_TYPES.styles}" Target="styles.xml"/>
351
- <Relationship Id="rId${sharedId}" Type="${REL_TYPES.sharedStrings}" Target="sharedStrings.xml"/>
570
+ <Relationship Id="rId${sharedId}" Type="${REL_TYPES.sharedStrings}" Target="sharedStrings.xml"/>${calcChainRel}
352
571
  </Relationships>`;
353
572
  }
354
573
  function buildWorkbookXml(sheets, definedNames = []) {
@@ -449,8 +668,9 @@ function parseRelationships(relsXml, ownerPath) {
449
668
  const tag = m[0];
450
669
  const id = /\bId="([^"]*)"/.exec(tag)?.[1];
451
670
  const type = /\bType="([^"]*)"/.exec(tag)?.[1];
452
- const target = /\bTarget="([^"]*)"/.exec(tag)?.[1];
453
- if (!id || !type || target === void 0) continue;
671
+ const rawTarget = /\bTarget="([^"]*)"/.exec(tag)?.[1];
672
+ if (!id || !type || rawTarget === void 0) continue;
673
+ const target = decodeXmlEntities(rawTarget);
454
674
  const external = /TargetMode="External"/.test(tag);
455
675
  rels.push({
456
676
  id,
@@ -952,8 +1172,12 @@ var ExcelBorderEdge = class _ExcelBorderEdge {
952
1172
  cacheKey() {
953
1173
  return `${this.style}:${this.color?.toString() ?? "null"}`;
954
1174
  }
1175
+ /** An independent copy. ExcelColor is immutable, so it is shared. */
1176
+ clone() {
1177
+ return new _ExcelBorderEdge(this.style, this.color);
1178
+ }
955
1179
  };
956
- var _ExcelBorder = class _ExcelBorder {
1180
+ var ExcelBorder = class _ExcelBorder {
957
1181
  constructor() {
958
1182
  this.left = new ExcelBorderEdge();
959
1183
  this.right = new ExcelBorderEdge();
@@ -993,6 +1217,33 @@ var _ExcelBorder = class _ExcelBorder {
993
1217
  b.right = new ExcelBorderEdge(style, color);
994
1218
  return b;
995
1219
  }
1220
+ /**
1221
+ * A border with no edges set.
1222
+ *
1223
+ * A fresh instance per access, not a shared singleton. `ExcelBorder` is mutable
1224
+ * (`border.left.style = …` is the normal way to use it), so a shared instance
1225
+ * handed to two styles let an edit through one of them reach the other — and,
1226
+ * once anything had assigned `none` and then modified it, every later
1227
+ * `ExcelBorder.none` came back already carrying that edge.
1228
+ */
1229
+ static get none() {
1230
+ return new _ExcelBorder();
1231
+ }
1232
+ /**
1233
+ * A deep copy: the edges are new objects, so mutating the copy cannot reach the
1234
+ * original. {@link CellStyle.clone} relies on this.
1235
+ */
1236
+ clone() {
1237
+ const b = new _ExcelBorder();
1238
+ b.left = this.left.clone();
1239
+ b.right = this.right.clone();
1240
+ b.top = this.top.clone();
1241
+ b.bottom = this.bottom.clone();
1242
+ b.diagonal = this.diagonal.clone();
1243
+ b.diagonalDown = this.diagonalDown;
1244
+ b.diagonalUp = this.diagonalUp;
1245
+ return b;
1246
+ }
996
1247
  // ── Serialisation ─────────────────────────────────────────────────────────
997
1248
  toXml() {
998
1249
  const dd = this.diagonalDown ? ' diagonalDown="1"' : "";
@@ -1038,8 +1289,6 @@ var _ExcelBorder = class _ExcelBorder {
1038
1289
  ].join("|");
1039
1290
  }
1040
1291
  };
1041
- _ExcelBorder.none = new _ExcelBorder();
1042
- var ExcelBorder = _ExcelBorder;
1043
1292
 
1044
1293
  // src/style/excel-alignment.ts
1045
1294
  var ExcelHorizontalAlignment = /* @__PURE__ */ ((ExcelHorizontalAlignment3) => {
@@ -1332,7 +1581,7 @@ var _CellStyle = class _CellStyle {
1332
1581
  const s = new _CellStyle();
1333
1582
  if (this.font) s.font = this.font.clone();
1334
1583
  s.fill = this.fill;
1335
- s.border = this.border;
1584
+ s.border = this.border?.clone();
1336
1585
  s.alignment = this.alignment ? Object.assign(new ExcelAlignment(), this.alignment) : void 0;
1337
1586
  s.numberFormat = this.numberFormat;
1338
1587
  s.isLocked = this.isLocked;
@@ -1355,8 +1604,46 @@ ensureAlignment_fn = function() {
1355
1604
  };
1356
1605
  var CellStyle = _CellStyle;
1357
1606
 
1607
+ // src/model/oa-date.ts
1608
+ var OA_EPOCH_MS = Date.UTC(1899, 11, 30, 0, 0, 0, 0);
1609
+ var MS_PER_DAY = 864e5;
1610
+ function toOADate(date) {
1611
+ const utcMs = Date.UTC(
1612
+ date.getFullYear(),
1613
+ date.getMonth(),
1614
+ date.getDate(),
1615
+ date.getHours(),
1616
+ date.getMinutes(),
1617
+ date.getSeconds(),
1618
+ date.getMilliseconds()
1619
+ );
1620
+ return (utcMs - OA_EPOCH_MS) / MS_PER_DAY;
1621
+ }
1622
+ function fromOADate(oaDate) {
1623
+ const ms = OA_EPOCH_MS + oaDate * MS_PER_DAY;
1624
+ const d = new Date(ms);
1625
+ return new Date(
1626
+ d.getUTCFullYear(),
1627
+ d.getUTCMonth(),
1628
+ d.getUTCDate(),
1629
+ d.getUTCHours(),
1630
+ d.getUTCMinutes(),
1631
+ d.getUTCSeconds(),
1632
+ d.getUTCMilliseconds()
1633
+ );
1634
+ }
1635
+ var DATE_FORMAT_IDS = /* @__PURE__ */ new Set([14, 15, 16, 17, 18, 19, 20, 21, 22, 45, 46, 47]);
1636
+ function isDateFormatId(formatId) {
1637
+ return DATE_FORMAT_IDS.has(formatId);
1638
+ }
1639
+ function isDateFormatCode(formatCode) {
1640
+ const lower = formatCode.toLowerCase();
1641
+ return lower.includes("y") || lower.includes("d") || lower.includes("m") && !lower.includes("mm:") || // m = month unless part of mm:ss
1642
+ lower.includes("h") || lower.includes("am") || lower.includes("pm");
1643
+ }
1644
+
1358
1645
  // src/style/style-manager.ts
1359
- var _StyleManager_instances, initDefaults_fn, getOrCreateFont_fn, getOrCreateFill_fn, getOrCreateBorder_fn, getOrCreateNumFmt_fn, cellFormatKey_fn, cfToXml_fn;
1646
+ var _numFmtMapCache, _dateStyleMemo, _StyleManager_instances, numFmtMap_fn, invalidateFormatCaches_fn, initDefaults_fn, getOrCreateFont_fn, getOrCreateFill_fn, getOrCreateBorder_fn, getOrCreateNumFmt_fn, cellFormatKey_fn, cfToXml_fn;
1360
1647
  var StyleManager = class {
1361
1648
  constructor() {
1362
1649
  __privateAdd(this, _StyleManager_instances);
@@ -1375,6 +1662,21 @@ var StyleManager = class {
1375
1662
  this.cellXfCache = /* @__PURE__ */ new Map();
1376
1663
  this.nextCustomNumFmtId = 164;
1377
1664
  this.isDirty = false;
1665
+ /**
1666
+ * `numFmtId` → format code for the custom (≥164) formats, materialised once.
1667
+ * Rebuilding it per lookup made every numeric cell read O(custom formats),
1668
+ * and reading a cell's value has to consult it to tell dates from numbers.
1669
+ * Invalidated whenever `numFmts` changes — see {@link #invalidateFormatCaches}.
1670
+ */
1671
+ __privateAdd(this, _numFmtMapCache);
1672
+ /**
1673
+ * cellXf index → does its number format render a date? Memoised because
1674
+ * {@link Cell.getValue} asks this for every numeric cell, and answering it
1675
+ * from scratch reconstructs a whole CellStyle. Entries stay valid because
1676
+ * `cellXfs` and `numFmts` are append-only: an existing index never changes
1677
+ * meaning.
1678
+ */
1679
+ __privateAdd(this, _dateStyleMemo, /* @__PURE__ */ new Map());
1378
1680
  __privateMethod(this, _StyleManager_instances, initDefaults_fn).call(this);
1379
1681
  }
1380
1682
  // ── Public API ─────────────────────────────────────────────────────────────
@@ -1426,10 +1728,7 @@ var StyleManager = class {
1426
1728
  const borderEntry = this.borders[cf.borderId];
1427
1729
  if (borderEntry && cf.applyBorder) s.border = borderEntry.border;
1428
1730
  if (cf.applyNumFmt) {
1429
- const customMap = new Map(
1430
- this.numFmts.map((n) => [n.id, n.code])
1431
- );
1432
- s.numberFormat = ExcelNumberFormat.fromFormatId(cf.numFmtId, customMap);
1731
+ s.numberFormat = ExcelNumberFormat.fromFormatId(cf.numFmtId, __privateMethod(this, _StyleManager_instances, numFmtMap_fn).call(this));
1433
1732
  }
1434
1733
  if (cf.alignment && cf.applyAlign) s.alignment = cf.alignment;
1435
1734
  if (cf.applyProt) {
@@ -1438,6 +1737,29 @@ var StyleManager = class {
1438
1737
  }
1439
1738
  return s;
1440
1739
  }
1740
+ /**
1741
+ * Whether the cellXf at `styleIndex` formats its value as a date. This is the
1742
+ * one style question the value-reading path has to ask, so it is answered
1743
+ * without reconstructing a CellStyle: the result is derived from the stored
1744
+ * format id/code and memoised per index.
1745
+ *
1746
+ * Equivalent to `getCellStyle(i).numberFormat` being date-like, by
1747
+ * construction — both read the same `applyNumFmt`/`numFmtId` pair through
1748
+ * {@link ExcelNumberFormat.fromFormatId}.
1749
+ */
1750
+ isDateStyle(styleIndex) {
1751
+ const memo = __privateGet(this, _dateStyleMemo).get(styleIndex);
1752
+ if (memo !== void 0) return memo;
1753
+ const cf = this.cellXfs[styleIndex];
1754
+ if (!cf) return false;
1755
+ let isDate = false;
1756
+ if (cf.applyNumFmt) {
1757
+ const fmt = ExcelNumberFormat.fromFormatId(cf.numFmtId, __privateMethod(this, _StyleManager_instances, numFmtMap_fn).call(this));
1758
+ isDate = isDateFormatId(fmt.formatId) || isDateFormatCode(fmt.formatCode);
1759
+ }
1760
+ __privateGet(this, _dateStyleMemo).set(styleIndex, isDate);
1761
+ return isDate;
1762
+ }
1441
1763
  // ── XML serialisation ──────────────────────────────────────────────────────
1442
1764
  /** Builds the complete xl/styles.xml string. */
1443
1765
  buildStylesXml() {
@@ -1467,6 +1789,7 @@ ${cellXfsXml}
1467
1789
  this.numFmtCache.clear();
1468
1790
  this.cellXfCache.clear();
1469
1791
  this.nextCustomNumFmtId = 164;
1792
+ __privateMethod(this, _StyleManager_instances, invalidateFormatCaches_fn).call(this);
1470
1793
  const root = parseXml(stylesXml);
1471
1794
  for (const el of getElementsByTagName(root, "numFmt")) {
1472
1795
  const id = parseInt(getAttr(el, "numFmtId") ?? "0", 10);
@@ -1525,9 +1848,23 @@ ${cellXfsXml}
1525
1848
  }
1526
1849
  if (this.fonts.length === 0) __privateMethod(this, _StyleManager_instances, initDefaults_fn).call(this);
1527
1850
  if (this.cellXfs.length === 0) __privateMethod(this, _StyleManager_instances, initDefaults_fn).call(this);
1851
+ __privateMethod(this, _StyleManager_instances, invalidateFormatCaches_fn).call(this);
1528
1852
  }
1529
1853
  };
1854
+ _numFmtMapCache = new WeakMap();
1855
+ _dateStyleMemo = new WeakMap();
1530
1856
  _StyleManager_instances = new WeakSet();
1857
+ numFmtMap_fn = function() {
1858
+ if (!__privateGet(this, _numFmtMapCache)) {
1859
+ __privateSet(this, _numFmtMapCache, new Map(this.numFmts.map((n) => [n.id, n.code])));
1860
+ }
1861
+ return __privateGet(this, _numFmtMapCache);
1862
+ };
1863
+ /** Drops the derived format caches after `numFmts` or `cellXfs` changed. */
1864
+ invalidateFormatCaches_fn = function() {
1865
+ __privateSet(this, _numFmtMapCache, void 0);
1866
+ __privateGet(this, _dateStyleMemo).clear();
1867
+ };
1531
1868
  // ── Private helpers ────────────────────────────────────────────────────────
1532
1869
  initDefaults_fn = function() {
1533
1870
  const defaultFont = new ExcelFont({ name: "Calibri", size: 11 });
@@ -1600,6 +1937,7 @@ getOrCreateNumFmt_fn = function(fmt) {
1600
1937
  const id = this.nextCustomNumFmtId++;
1601
1938
  this.numFmts.push({ id, code: fmt.formatCode });
1602
1939
  this.numFmtCache.set(fmt.formatCode, id);
1940
+ __privateMethod(this, _StyleManager_instances, invalidateFormatCaches_fn).call(this);
1603
1941
  this.isDirty = true;
1604
1942
  return id;
1605
1943
  };
@@ -1669,10 +2007,15 @@ function splitSections(code) {
1669
2007
  sections.push(current);
1670
2008
  return sections;
1671
2009
  }
2010
+ function hasDateTokens(section) {
2011
+ const bare = section.replace(/"[^"]*"/g, "").replace(/\\./g, "").replace(/\[[^\]]*\]/g, "");
2012
+ return /[ymdhsYMDHS]/.test(bare);
2013
+ }
1672
2014
  function isUnsupported(section) {
1673
2015
  if (/\[[<>=]/.test(section)) return true;
1674
2016
  if (/[?]/.test(section)) return true;
1675
2017
  if (/[Ee][+-]/.test(section)) return true;
2018
+ if (hasDateTokens(section)) return true;
1676
2019
  return false;
1677
2020
  }
1678
2021
  function parseSection(section) {
@@ -1766,12 +2109,177 @@ function formatNumberWithCode(value, formatCode) {
1766
2109
  return renderNumber(magnitude, parseSection(section));
1767
2110
  }
1768
2111
 
2112
+ // src/style/date-format-renderer.ts
2113
+ var MONTHS = [
2114
+ "January",
2115
+ "February",
2116
+ "March",
2117
+ "April",
2118
+ "May",
2119
+ "June",
2120
+ "July",
2121
+ "August",
2122
+ "September",
2123
+ "October",
2124
+ "November",
2125
+ "December"
2126
+ ];
2127
+ var DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
2128
+ function firstSection(code) {
2129
+ let inQuote = false;
2130
+ for (let i = 0; i < code.length; i++) {
2131
+ const ch = code[i];
2132
+ if (ch === '"') inQuote = !inQuote;
2133
+ else if (ch === ";" && !inQuote) return code.slice(0, i);
2134
+ }
2135
+ return code;
2136
+ }
2137
+ function tokenize(section) {
2138
+ const tokens = [];
2139
+ let i = 0;
2140
+ const n = section.length;
2141
+ while (i < n) {
2142
+ const ch = section[i];
2143
+ if (ch === '"') {
2144
+ let j = i + 1;
2145
+ let text = "";
2146
+ while (j < n && section[j] !== '"') text += section[j++];
2147
+ tokens.push({ kind: "literal", text });
2148
+ i = j + 1;
2149
+ continue;
2150
+ }
2151
+ if (ch === "\\") {
2152
+ tokens.push({ kind: "literal", text: section[i + 1] ?? "" });
2153
+ i += 2;
2154
+ continue;
2155
+ }
2156
+ if (ch === "[") {
2157
+ const close = section.indexOf("]", i);
2158
+ if (close === -1) return null;
2159
+ const inner = section.slice(i + 1, close);
2160
+ if (/^[hmsHMS]+$/.test(inner)) return null;
2161
+ i = close + 1;
2162
+ continue;
2163
+ }
2164
+ const rest = section.slice(i);
2165
+ const ampm = /^(AM\/PM|A\/P|am\/pm|a\/p)/.exec(rest);
2166
+ if (ampm) {
2167
+ tokens.push({ kind: "ampm", text: ampm[1] });
2168
+ i += ampm[1].length;
2169
+ continue;
2170
+ }
2171
+ const lower = ch.toLowerCase();
2172
+ if ("ymdhs".includes(lower)) {
2173
+ let j = i;
2174
+ while (j < n && section[j].toLowerCase() === lower) j++;
2175
+ tokens.push({ kind: lower, text: section.slice(i, j) });
2176
+ i = j;
2177
+ if (lower === "s" && section[i] === "." && /\d|0/.test(section[i + 1] ?? "")) return null;
2178
+ continue;
2179
+ }
2180
+ if (/[a-zA-Z]/.test(ch)) return null;
2181
+ tokens.push({ kind: "literal", text: ch });
2182
+ i++;
2183
+ }
2184
+ return tokens;
2185
+ }
2186
+ function resolveMonthVsMinute(tokens) {
2187
+ const dateKinds = /* @__PURE__ */ new Set(["y", "m", "d", "h", "s"]);
2188
+ for (let t = 0; t < tokens.length; t++) {
2189
+ const token = tokens[t];
2190
+ if (token.kind !== "m" || token.text.length > 2) continue;
2191
+ let prev;
2192
+ for (let p = t - 1; p >= 0; p--) {
2193
+ if (dateKinds.has(tokens[p].kind)) {
2194
+ prev = tokens[p];
2195
+ break;
2196
+ }
2197
+ }
2198
+ let next;
2199
+ for (let q = t + 1; q < tokens.length; q++) {
2200
+ if (dateKinds.has(tokens[q].kind)) {
2201
+ next = tokens[q];
2202
+ break;
2203
+ }
2204
+ }
2205
+ if (prev?.kind === "h" || next?.kind === "s") token.kind = "minute";
2206
+ }
2207
+ }
2208
+ var pad = (v, len) => String(v).padStart(len, "0");
2209
+ function formatDateCode(date, formatCode) {
2210
+ if (!formatCode || formatCode === "General") return null;
2211
+ const tokens = tokenize(firstSection(formatCode));
2212
+ if (!tokens) return null;
2213
+ if (!tokens.some((t) => "ymdhs".includes(t.kind))) return null;
2214
+ resolveMonthVsMinute(tokens);
2215
+ const twelveHour = tokens.some((t) => t.kind === "ampm");
2216
+ const hours24 = date.getHours();
2217
+ const hours12 = hours24 % 12 === 0 ? 12 : hours24 % 12;
2218
+ let out = "";
2219
+ for (const t of tokens) {
2220
+ switch (t.kind) {
2221
+ case "literal":
2222
+ out += t.text;
2223
+ break;
2224
+ case "y":
2225
+ out += t.text.length <= 2 ? pad(date.getFullYear() % 100, 2) : String(date.getFullYear());
2226
+ break;
2227
+ case "m": {
2228
+ const month = date.getMonth();
2229
+ if (t.text.length === 1) out += String(month + 1);
2230
+ else if (t.text.length === 2) out += pad(month + 1, 2);
2231
+ else if (t.text.length === 3) out += MONTHS[month].slice(0, 3);
2232
+ else if (t.text.length === 5) out += MONTHS[month][0];
2233
+ else out += MONTHS[month];
2234
+ break;
2235
+ }
2236
+ case "minute":
2237
+ out += t.text.length === 1 ? String(date.getMinutes()) : pad(date.getMinutes(), 2);
2238
+ break;
2239
+ case "d": {
2240
+ const day = date.getDate();
2241
+ if (t.text.length === 1) out += String(day);
2242
+ else if (t.text.length === 2) out += pad(day, 2);
2243
+ else if (t.text.length === 3) out += DAYS[date.getDay()].slice(0, 3);
2244
+ else out += DAYS[date.getDay()];
2245
+ break;
2246
+ }
2247
+ case "h": {
2248
+ const h = twelveHour ? hours12 : hours24;
2249
+ out += t.text.length === 1 ? String(h) : pad(h, 2);
2250
+ break;
2251
+ }
2252
+ case "s":
2253
+ out += t.text.length === 1 ? String(date.getSeconds()) : pad(date.getSeconds(), 2);
2254
+ break;
2255
+ case "ampm": {
2256
+ const isAm = hours24 < 12;
2257
+ if (t.text === "a/p") out += isAm ? "a" : "p";
2258
+ else if (t.text === "A/P") out += isAm ? "A" : "P";
2259
+ else if (t.text === "am/pm") out += isAm ? "am" : "pm";
2260
+ else out += isAm ? "AM" : "PM";
2261
+ break;
2262
+ }
2263
+ }
2264
+ }
2265
+ return out;
2266
+ }
2267
+
1769
2268
  // src/model/cell-reference.ts
2269
+ var EXCEL_MAX_ROWS = 1048576;
2270
+ var EXCEL_MAX_COLUMNS = 16384;
2271
+ function needsNormalizing(ref) {
2272
+ for (let i = 0; i < ref.length; i++) {
2273
+ const code = ref.charCodeAt(i);
2274
+ if (code === 36 || code >= 97 && code <= 122) return true;
2275
+ }
2276
+ return false;
2277
+ }
1770
2278
  function parseCellRef(cellReference) {
1771
2279
  if (!cellReference) {
1772
2280
  throw new Error("Cell reference cannot be null or empty.");
1773
2281
  }
1774
- const ref = cellReference.replace(/\$/g, "").toUpperCase();
2282
+ const ref = needsNormalizing(cellReference) ? cellReference.replace(/\$/g, "").toUpperCase() : cellReference;
1775
2283
  let i = 0;
1776
2284
  let column = 0;
1777
2285
  while (i < ref.length && ref.charCodeAt(i) >= 65 && ref.charCodeAt(i) <= 90) {
@@ -1817,6 +2325,11 @@ function columnNumber(letters) {
1817
2325
  }
1818
2326
  return col;
1819
2327
  }
2328
+ function formatRangeRef(startRow, startColumn, endRow, endColumn) {
2329
+ const start = cellRefFromRowCol(startRow, startColumn);
2330
+ if (startRow === endRow && startColumn === endColumn) return start;
2331
+ return `${start}:${cellRefFromRowCol(endRow, endColumn)}`;
2332
+ }
1820
2333
  function parseRangeRef(rangeReference) {
1821
2334
  const parts = rangeReference.split(":");
1822
2335
  if (parts.length !== 2) {
@@ -1837,56 +2350,54 @@ function parseRangeRef(rangeReference) {
1837
2350
  };
1838
2351
  }
1839
2352
 
1840
- // src/model/oa-date.ts
1841
- var OA_EPOCH_MS = Date.UTC(1899, 11, 30, 0, 0, 0, 0);
1842
- var MS_PER_DAY = 864e5;
1843
- function toOADate(date) {
1844
- const utcMs = Date.UTC(
1845
- date.getFullYear(),
1846
- date.getMonth(),
1847
- date.getDate(),
1848
- date.getHours(),
1849
- date.getMinutes(),
1850
- date.getSeconds(),
1851
- date.getMilliseconds()
1852
- );
1853
- return (utcMs - OA_EPOCH_MS) / MS_PER_DAY;
1854
- }
1855
- function fromOADate(oaDate) {
1856
- const ms = OA_EPOCH_MS + oaDate * MS_PER_DAY;
1857
- const d = new Date(ms);
1858
- return new Date(
1859
- d.getUTCFullYear(),
1860
- d.getUTCMonth(),
1861
- d.getUTCDate(),
1862
- d.getUTCHours(),
1863
- d.getUTCMinutes(),
1864
- d.getUTCSeconds(),
1865
- d.getUTCMilliseconds()
1866
- );
1867
- }
1868
- var DATE_FORMAT_IDS = /* @__PURE__ */ new Set([14, 15, 16, 17, 18, 19, 20, 21, 22, 45, 46, 47]);
1869
- function isDateFormatId(formatId) {
1870
- return DATE_FORMAT_IDS.has(formatId);
2353
+ // src/model/cell.ts
2354
+ function readCellValue(data, sharedStrings, styles) {
2355
+ const raw = data.rawValue;
2356
+ if (raw === void 0 || raw === "") return null;
2357
+ switch (data.dataType) {
2358
+ case "s": {
2359
+ const idx = parseInt(raw, 10);
2360
+ return sharedStrings.tryGetString(idx) ?? raw;
2361
+ }
2362
+ case "b":
2363
+ return raw === "1";
2364
+ case "str":
2365
+ return raw;
2366
+ default: {
2367
+ const num = parseFloat(raw);
2368
+ if (isNaN(num)) return raw;
2369
+ if (styles.isDateStyle(data.styleIndex)) {
2370
+ try {
2371
+ return fromOADate(num);
2372
+ } catch {
2373
+ return num;
2374
+ }
2375
+ }
2376
+ return num;
2377
+ }
2378
+ }
1871
2379
  }
1872
- function isDateFormatCode(formatCode) {
1873
- const lower = formatCode.toLowerCase();
1874
- return lower.includes("y") || lower.includes("d") || lower.includes("m") && !lower.includes("mm:") || // m = month unless part of mm:ss
1875
- lower.includes("h") || lower.includes("am") || lower.includes("pm");
2380
+ function buildCellXml(data) {
2381
+ const style = data.styleIndex > 0 ? ` s="${data.styleIndex}"` : "";
2382
+ const formulaXml = data.formula ? data.arrayRef ? `<f t="array" ref="${data.arrayRef}" aca="1" ca="1">${escXml(data.formula)}</f>` : `<f>${escXml(data.formula)}</f>` : "";
2383
+ const valueXml = data.rawValue !== void 0 && data.rawValue !== "" ? `<v>${escXml(data.rawValue)}</v>` : "";
2384
+ const type = data.dataType && (formulaXml || valueXml) ? ` t="${data.dataType}"` : "";
2385
+ if (!formulaXml && !valueXml && data.styleIndex === 0) return "";
2386
+ return `<c r="${data.reference}"${style}${type}>${formulaXml}${valueXml}</c>`;
1876
2387
  }
1877
-
1878
- // src/model/cell.ts
1879
- var _data, _sharedStrings, _styles, _Cell_instances, isDateStyle_fn;
2388
+ var _data, _sharedStrings, _styles, _version, _Cell_instances, touch_fn;
1880
2389
  var Cell = class {
1881
2390
  /** @internal */
1882
- constructor(data, sharedStrings, styles) {
2391
+ constructor(data, sharedStrings, styles, version) {
1883
2392
  __privateAdd(this, _Cell_instances);
1884
2393
  __privateAdd(this, _data);
1885
2394
  __privateAdd(this, _sharedStrings);
1886
2395
  __privateAdd(this, _styles);
2396
+ __privateAdd(this, _version);
1887
2397
  __privateSet(this, _data, data);
1888
2398
  __privateSet(this, _sharedStrings, sharedStrings);
1889
2399
  __privateSet(this, _styles, styles);
2400
+ __privateSet(this, _version, version);
1890
2401
  }
1891
2402
  // ── Identity ───────────────────────────────────────────────────────────────
1892
2403
  /** Cell reference string e.g. "A1". */
@@ -1902,6 +2413,7 @@ var Cell = class {
1902
2413
  return parseCellRef(__privateGet(this, _data).reference).column;
1903
2414
  }
1904
2415
  setValue(value) {
2416
+ __privateMethod(this, _Cell_instances, touch_fn).call(this);
1905
2417
  __privateGet(this, _data).formula = void 0;
1906
2418
  if (value === null || value === void 0) {
1907
2419
  __privateGet(this, _data).dataType = void 0;
@@ -1939,29 +2451,7 @@ var Cell = class {
1939
2451
  * Date detection is based on the cell's number format.
1940
2452
  */
1941
2453
  getValue() {
1942
- if (__privateGet(this, _data).rawValue === void 0 || __privateGet(this, _data).rawValue === "") return null;
1943
- switch (__privateGet(this, _data).dataType) {
1944
- case "s": {
1945
- const idx = parseInt(__privateGet(this, _data).rawValue, 10);
1946
- return __privateGet(this, _sharedStrings).tryGetString(idx) ?? __privateGet(this, _data).rawValue;
1947
- }
1948
- case "b":
1949
- return __privateGet(this, _data).rawValue === "1";
1950
- case "str":
1951
- return __privateGet(this, _data).rawValue;
1952
- default: {
1953
- const num = parseFloat(__privateGet(this, _data).rawValue);
1954
- if (isNaN(num)) return __privateGet(this, _data).rawValue;
1955
- if (__privateMethod(this, _Cell_instances, isDateStyle_fn).call(this)) {
1956
- try {
1957
- return fromOADate(num);
1958
- } catch {
1959
- return num;
1960
- }
1961
- }
1962
- return num;
1963
- }
1964
- }
2454
+ return readCellValue(__privateGet(this, _data), __privateGet(this, _sharedStrings), __privateGet(this, _styles));
1965
2455
  }
1966
2456
  /**
1967
2457
  * Returns the display text of the cell value (always a string).
@@ -1977,18 +2467,20 @@ var Cell = class {
1977
2467
  }
1978
2468
  /**
1979
2469
  * Returns the display text with the cell's number format applied, e.g. a
1980
- * value of 4.5 under `$#,##0.00` renders as `$4.50`.
2470
+ * value of 4.5 under `$#,##0.00` renders as `$4.50`, and a date under
2471
+ * `dd/mm/yyyy` as `31/01/2025`.
1981
2472
  *
1982
- * Falls back to {@link getText} for non-numeric values and for format codes
1983
- * outside the supported subset (fractions, scientific notation, conditional
1984
- * sections) — so the result is never a misleading rendering.
2473
+ * Falls back to {@link getText} for values with no format and for format
2474
+ * codes outside the supported subset (fractions, scientific notation,
2475
+ * conditional sections, elapsed time) — so the result is never a misleading
2476
+ * rendering.
1985
2477
  */
1986
2478
  getFormattedText() {
1987
2479
  const val = this.getValue();
1988
- if (typeof val !== "number") return this.getText();
2480
+ if (typeof val !== "number" && !(val instanceof Date)) return this.getText();
1989
2481
  const format = __privateGet(this, _styles).getCellStyle(__privateGet(this, _data).styleIndex).numberFormat;
1990
2482
  if (!format) return this.getText();
1991
- return formatNumberWithCode(val, format.formatCode) ?? this.getText();
2483
+ return val instanceof Date ? formatDateCode(val, format.formatCode) ?? this.getText() : formatNumberWithCode(val, format.formatCode) ?? this.getText();
1992
2484
  }
1993
2485
  // ── Formula API ────────────────────────────────────────────────────────────
1994
2486
  /**
@@ -1997,6 +2489,7 @@ var Cell = class {
1997
2489
  */
1998
2490
  setFormula(formula) {
1999
2491
  if (!formula) throw new Error("Formula cannot be empty.");
2492
+ __privateMethod(this, _Cell_instances, touch_fn).call(this);
2000
2493
  __privateGet(this, _data).formula = formula.startsWith("=") ? formula.slice(1) : formula;
2001
2494
  __privateGet(this, _data).rawValue = void 0;
2002
2495
  __privateGet(this, _data).dataType = void 0;
@@ -2009,6 +2502,7 @@ var Cell = class {
2009
2502
  * plain scalar formula. Serialises as `<f t="array" ref="…">`.
2010
2503
  */
2011
2504
  setArrayRef(spillRef) {
2505
+ __privateMethod(this, _Cell_instances, touch_fn).call(this);
2012
2506
  __privateGet(this, _data).arrayRef = spillRef ?? void 0;
2013
2507
  return this;
2014
2508
  }
@@ -2026,6 +2520,7 @@ var Cell = class {
2026
2520
  * added to the shared-string table.
2027
2521
  */
2028
2522
  setComputedValue(value) {
2523
+ __privateMethod(this, _Cell_instances, touch_fn).call(this);
2029
2524
  if (value === null || value === void 0) {
2030
2525
  __privateGet(this, _data).dataType = void 0;
2031
2526
  __privateGet(this, _data).rawValue = void 0;
@@ -2065,6 +2560,7 @@ var Cell = class {
2065
2560
  }
2066
2561
  /** Replaces the entire cell style. */
2067
2562
  set style(value) {
2563
+ __privateMethod(this, _Cell_instances, touch_fn).call(this);
2068
2564
  __privateGet(this, _data).styleIndex = __privateGet(this, _styles).getOrCreateCellFormatIndex(value);
2069
2565
  }
2070
2566
  /**
@@ -2075,6 +2571,7 @@ var Cell = class {
2075
2571
  * cell.applyStyle(s => s.setBold(true).setFontColor(ExcelColor.red))
2076
2572
  */
2077
2573
  applyStyle(action) {
2574
+ __privateMethod(this, _Cell_instances, touch_fn).call(this);
2078
2575
  const s = __privateGet(this, _styles).getCellStyle(__privateGet(this, _data).styleIndex).clone();
2079
2576
  action(s);
2080
2577
  __privateGet(this, _data).styleIndex = __privateGet(this, _styles).getOrCreateCellFormatIndex(s);
@@ -2117,6 +2614,7 @@ var Cell = class {
2117
2614
  // ── Clear ──────────────────────────────────────────────────────────────────
2118
2615
  /** Clears the cell value and formula, preserving style. */
2119
2616
  clear() {
2617
+ __privateMethod(this, _Cell_instances, touch_fn).call(this);
2120
2618
  __privateGet(this, _data).rawValue = void 0;
2121
2619
  __privateGet(this, _data).dataType = void 0;
2122
2620
  __privateGet(this, _data).formula = void 0;
@@ -2124,19 +2622,14 @@ var Cell = class {
2124
2622
  }
2125
2623
  /** Clears the cell value, formula, AND style. */
2126
2624
  clearAll() {
2625
+ __privateMethod(this, _Cell_instances, touch_fn).call(this);
2127
2626
  this.clear();
2128
2627
  __privateGet(this, _data).styleIndex = 0;
2129
2628
  }
2130
2629
  // ── XML serialisation ──────────────────────────────────────────────────────
2131
2630
  /** @internal Builds the <c> XML element string for this cell. */
2132
2631
  toXml() {
2133
- const ref = __privateGet(this, _data).reference;
2134
- const style = __privateGet(this, _data).styleIndex > 0 ? ` s="${__privateGet(this, _data).styleIndex}"` : "";
2135
- const formulaXml = __privateGet(this, _data).formula ? __privateGet(this, _data).arrayRef ? `<f t="array" ref="${__privateGet(this, _data).arrayRef}" aca="1" ca="1">${escXml(__privateGet(this, _data).formula)}</f>` : `<f>${escXml(__privateGet(this, _data).formula)}</f>` : "";
2136
- const valueXml = __privateGet(this, _data).rawValue !== void 0 && __privateGet(this, _data).rawValue !== "" ? `<v>${escXml(__privateGet(this, _data).rawValue)}</v>` : "";
2137
- const type = __privateGet(this, _data).dataType && (formulaXml || valueXml) ? ` t="${__privateGet(this, _data).dataType}"` : "";
2138
- if (!formulaXml && !valueXml && __privateGet(this, _data).styleIndex === 0) return "";
2139
- return `<c r="${ref}"${style}${type}>${formulaXml}${valueXml}</c>`;
2632
+ return buildCellXml(__privateGet(this, _data));
2140
2633
  }
2141
2634
  /** @internal Parses a <c> XML element into CellData. */
2142
2635
  static fromXmlElement(el, ref) {
@@ -2145,105 +2638,622 @@ var Cell = class {
2145
2638
  let rawValue;
2146
2639
  let formula;
2147
2640
  let arrayRef;
2641
+ let sharedIndex;
2148
2642
  for (const child of el.children) {
2149
2643
  const tag = child.tagName.replace(/^.*:/, "");
2150
2644
  if (tag === "v") rawValue = child.textContent;
2151
2645
  if (tag === "f") {
2152
2646
  formula = child.textContent;
2153
- if (child.attributes && child.attributes["t"] === "array" && child.attributes["ref"]) {
2154
- arrayRef = child.attributes["ref"];
2647
+ const attrs = child.attributes;
2648
+ const type = attrs?.["t"];
2649
+ if (type === "array" && attrs?.["ref"]) {
2650
+ arrayRef = attrs["ref"];
2651
+ }
2652
+ if (type === "shared" && attrs?.["si"] !== void 0) {
2653
+ const si = parseInt(attrs["si"], 10);
2654
+ if (!Number.isNaN(si)) sharedIndex = si;
2155
2655
  }
2156
2656
  }
2157
2657
  }
2158
- return { reference: ref, dataType, rawValue, formula, arrayRef, styleIndex };
2658
+ if (formula === "") formula = void 0;
2659
+ return { reference: ref, dataType, rawValue, formula, arrayRef, styleIndex, sharedIndex };
2159
2660
  }
2160
2661
  };
2161
2662
  _data = new WeakMap();
2162
2663
  _sharedStrings = new WeakMap();
2163
2664
  _styles = new WeakMap();
2665
+ _version = new WeakMap();
2164
2666
  _Cell_instances = new WeakSet();
2165
- // ── Private helpers ────────────────────────────────────────────────────────
2166
- isDateStyle_fn = function() {
2167
- const style = __privateGet(this, _styles).getCellStyle(__privateGet(this, _data).styleIndex);
2168
- if (!style.numberFormat) return false;
2169
- if (isDateFormatId(style.numberFormat.formatId)) return true;
2170
- return isDateFormatCode(style.numberFormat.formatCode);
2667
+ /** Records that this cell's stored data changed. */
2668
+ touch_fn = function() {
2669
+ if (__privateGet(this, _version)) __privateGet(this, _version).v++;
2171
2670
  };
2172
2671
  function escXml(s) {
2173
2672
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2174
2673
  }
2175
2674
 
2176
- // src/model/range.ts
2177
- var _sheet, _startRow, _startColumn, _endRow, _endColumn, _address, _Range_instances, forEach_fn;
2178
- var Range = class {
2179
- constructor(sheet, rangeReference) {
2180
- __privateAdd(this, _Range_instances);
2181
- __privateAdd(this, _sheet);
2182
- __privateAdd(this, _startRow);
2183
- __privateAdd(this, _startColumn);
2184
- __privateAdd(this, _endRow);
2185
- __privateAdd(this, _endColumn);
2186
- __privateAdd(this, _address);
2187
- __privateSet(this, _sheet, sheet);
2188
- __privateSet(this, _address, rangeReference.toUpperCase());
2189
- const { startRow, startColumn, endRow, endColumn } = parseRangeRef(rangeReference);
2190
- __privateSet(this, _startRow, startRow);
2191
- __privateSet(this, _startColumn, startColumn);
2192
- __privateSet(this, _endRow, endRow);
2193
- __privateSet(this, _endColumn, endColumn);
2194
- }
2195
- /** The range address e.g. "A1:B10". */
2196
- get address() {
2197
- return __privateGet(this, _address);
2198
- }
2199
- /** Number of rows in the range. */
2200
- get rowCount() {
2201
- return __privateGet(this, _endRow) - __privateGet(this, _startRow) + 1;
2202
- }
2203
- /** Number of columns in the range. */
2204
- get columnCount() {
2205
- return __privateGet(this, _endColumn) - __privateGet(this, _startColumn) + 1;
2675
+ // src/model/ref-shift.ts
2676
+ function shiftIndex(index, shift) {
2677
+ if (shift.kind === "insert") {
2678
+ return index >= shift.at ? index + shift.count : index;
2679
+ }
2680
+ const bandEnd = shift.at + shift.count - 1;
2681
+ if (index < shift.at) return index;
2682
+ if (index <= bandEnd) return null;
2683
+ return index - shift.count;
2684
+ }
2685
+ function shiftSpan(start, end, shift) {
2686
+ if (shift.kind === "insert") {
2687
+ return {
2688
+ start: start >= shift.at ? start + shift.count : start,
2689
+ end: end >= shift.at ? end + shift.count : end
2690
+ };
2206
2691
  }
2207
- // ── Value bulk operations ──────────────────────────────────────────────────
2208
- /**
2209
- * Sets values from a 2D array.
2210
- * Values that exceed the range boundary are silently ignored.
2211
- */
2212
- setValues(values) {
2213
- for (let r = 0; r < values.length && __privateGet(this, _startRow) + r <= __privateGet(this, _endRow); r++) {
2214
- const rowValues = values[r];
2215
- for (let c = 0; c < rowValues.length && __privateGet(this, _startColumn) + c <= __privateGet(this, _endColumn); c++) {
2216
- const cell = __privateGet(this, _sheet).getCell(__privateGet(this, _startRow) + r, __privateGet(this, _startColumn) + c);
2217
- const val = rowValues[c];
2218
- cell.setValue(val);
2692
+ const bandEnd = shift.at + shift.count - 1;
2693
+ const newStart = start < shift.at ? start : start <= bandEnd ? shift.at : start - shift.count;
2694
+ const newEnd = end < shift.at ? end : end <= bandEnd ? shift.at - 1 : end - shift.count;
2695
+ return newEnd < newStart ? null : { start: newStart, end: newEnd };
2696
+ }
2697
+ function shiftRangeRef(ref, shift) {
2698
+ const [startRef, endRef] = ref.split(":");
2699
+ const start = parseCellRef(startRef);
2700
+ const end = endRef !== void 0 ? parseCellRef(endRef) : start;
2701
+ const rows = shift.dim === "row" ? shiftSpan(start.row, end.row, shift) : { start: start.row, end: end.row };
2702
+ const cols = shift.dim === "col" ? shiftSpan(start.column, end.column, shift) : { start: start.column, end: end.column };
2703
+ if (!rows || !cols) return null;
2704
+ const startOut = cellRefFromRowCol(rows.start, cols.start);
2705
+ if (endRef === void 0) return startOut;
2706
+ return `${startOut}:${cellRefFromRowCol(rows.end, cols.end)}`;
2707
+ }
2708
+ function shiftSqref(sqref, shift) {
2709
+ const surviving = sqref.split(/\s+/).filter(Boolean).map((part) => shiftRangeRef(part, shift)).filter((part) => part !== null);
2710
+ return surviving.length > 0 ? surviving.join(" ") : null;
2711
+ }
2712
+
2713
+ // src/model/formula-ref-shift.ts
2714
+ var MAX_ROW = EXCEL_MAX_ROWS;
2715
+ var MAX_COL = EXCEL_MAX_COLUMNS;
2716
+ function isWordChar(code) {
2717
+ return code >= 65 && code <= 90 || // A-Z
2718
+ code >= 97 && code <= 122 || // a-z
2719
+ code >= 48 && code <= 57 || // 0-9
2720
+ code === 95 || // _
2721
+ code === 46 || // .
2722
+ code === 36;
2723
+ }
2724
+ function parseCellComponent(word) {
2725
+ const m = /^(\$?)([A-Za-z]{1,3})(\$?)(\d+)$/.exec(word);
2726
+ if (!m) return null;
2727
+ const col = columnNumber(m[2]);
2728
+ const row = parseInt(m[4], 10);
2729
+ if (col > MAX_COL || row < 1 || row > MAX_ROW) return null;
2730
+ return { colAbs: m[1] === "$", col, rowAbs: m[3] === "$", row };
2731
+ }
2732
+ function parseColComponent(word) {
2733
+ const m = /^(\$?)([A-Za-z]{1,3})$/.exec(word);
2734
+ if (!m) return null;
2735
+ const col = columnNumber(m[2]);
2736
+ if (col > MAX_COL) return null;
2737
+ return { abs: m[1] === "$", col };
2738
+ }
2739
+ function parseRowComponent(word) {
2740
+ const m = /^(\$?)(\d+)$/.exec(word);
2741
+ if (!m) return null;
2742
+ const row = parseInt(m[2], 10);
2743
+ if (row < 1 || row > MAX_ROW) return null;
2744
+ return { abs: m[1] === "$", row };
2745
+ }
2746
+ function emitCell(c, row, col) {
2747
+ return `${c.colAbs ? "$" : ""}${columnLetter(col)}${c.rowAbs ? "$" : ""}${row}`;
2748
+ }
2749
+ function shiftFormulaRefs(formula, shift, formulaSheet) {
2750
+ const n = formula.length;
2751
+ const editedSheet = shift.sheetName.toUpperCase();
2752
+ const unqualifiedRewritable = formulaSheet.toUpperCase() === editedSheet;
2753
+ const isRowShift = shift.dim === "row";
2754
+ let out = "";
2755
+ let flushed = 0;
2756
+ let changed = false;
2757
+ let hasRefError = false;
2758
+ let i = 0;
2759
+ const replace = (start, text) => {
2760
+ if (text.length === i - start && formula.startsWith(text, start)) return;
2761
+ out += formula.slice(flushed, start) + text;
2762
+ flushed = i;
2763
+ changed = true;
2764
+ };
2765
+ const readWord = () => {
2766
+ let j = i;
2767
+ while (j < n && isWordChar(formula.charCodeAt(j))) j++;
2768
+ const w = formula.slice(i, j);
2769
+ i = j;
2770
+ return w;
2771
+ };
2772
+ const readQuoted = () => {
2773
+ let j = i + 1;
2774
+ while (j < n) {
2775
+ if (formula[j] === "'") {
2776
+ if (formula[j + 1] === "'") {
2777
+ j += 2;
2778
+ continue;
2779
+ }
2780
+ j++;
2781
+ break;
2219
2782
  }
2783
+ j++;
2220
2784
  }
2221
- }
2222
- /**
2223
- * Gets all cell values as a 2D array (row-major order).
2224
- */
2225
- getValues() {
2226
- const result = [];
2227
- for (let r = 0; r < this.rowCount; r++) {
2228
- const row = [];
2229
- for (let c = 0; c < this.columnCount; c++) {
2230
- const cell = __privateGet(this, _sheet).tryGetCell(__privateGet(this, _startRow) + r, __privateGet(this, _startColumn) + c);
2231
- row.push(cell ? cell.getValue() : null);
2785
+ const q = formula.slice(i, j);
2786
+ i = j;
2787
+ return q;
2788
+ };
2789
+ const consumeRef = (refStart, rewritable) => {
2790
+ const first = readWord();
2791
+ let firstCell = parseCellComponent(first);
2792
+ let firstCol = firstCell ? null : parseColComponent(first);
2793
+ let firstRow = firstCell || firstCol ? null : parseRowComponent(first);
2794
+ let second = null;
2795
+ let secondCell = null;
2796
+ let secondCol = null;
2797
+ let secondRow = null;
2798
+ if (formula[i] === ":" && i + 1 < n && isWordChar(formula.charCodeAt(i + 1))) {
2799
+ const save = i;
2800
+ i++;
2801
+ const w = readWord();
2802
+ if (firstCell) secondCell = parseCellComponent(w);
2803
+ else if (firstCol) secondCol = parseColComponent(w);
2804
+ else if (firstRow) secondRow = parseRowComponent(w);
2805
+ if (secondCell || secondCol || secondRow) {
2806
+ second = w;
2807
+ } else {
2808
+ i = save;
2232
2809
  }
2233
- result.push(row);
2234
2810
  }
2235
- return result;
2811
+ if (second === null && formula[i] === "(") return;
2812
+ const dead = () => {
2813
+ if (formula[i] === "#") i++;
2814
+ replace(refStart, "#REF!");
2815
+ hasRefError = true;
2816
+ };
2817
+ if (second !== null) {
2818
+ if (!rewritable) return;
2819
+ if (firstCell && secondCell) {
2820
+ const a = firstCell, b = secondCell;
2821
+ const rows = isRowShift ? shiftSpan(a.row, b.row, shift) : { start: a.row, end: b.row };
2822
+ const cols = isRowShift ? { start: a.col, end: b.col } : shiftSpan(a.col, b.col, shift);
2823
+ if (!rows || !cols) {
2824
+ dead();
2825
+ return;
2826
+ }
2827
+ replace(refStart, `${emitCell(a, rows.start, cols.start)}:${emitCell(b, rows.end, cols.end)}`);
2828
+ return;
2829
+ }
2830
+ if (firstCol && secondCol) {
2831
+ if (isRowShift) return;
2832
+ const span = shiftSpan(firstCol.col, secondCol.col, shift);
2833
+ if (!span) {
2834
+ dead();
2835
+ return;
2836
+ }
2837
+ const a = firstCol.abs ? "$" : "";
2838
+ const b = secondCol.abs ? "$" : "";
2839
+ replace(refStart, `${a}${columnLetter(span.start)}:${b}${columnLetter(span.end)}`);
2840
+ return;
2841
+ }
2842
+ if (firstRow && secondRow) {
2843
+ if (!isRowShift) return;
2844
+ const span = shiftSpan(firstRow.row, secondRow.row, shift);
2845
+ if (!span) {
2846
+ dead();
2847
+ return;
2848
+ }
2849
+ const a = firstRow.abs ? "$" : "";
2850
+ const b = secondRow.abs ? "$" : "";
2851
+ replace(refStart, `${a}${span.start}:${b}${span.end}`);
2852
+ return;
2853
+ }
2854
+ return;
2855
+ }
2856
+ if (!firstCell || !rewritable) return;
2857
+ const row = isRowShift ? shiftIndex(firstCell.row, shift) : firstCell.row;
2858
+ const col = isRowShift ? firstCell.col : shiftIndex(firstCell.col, shift);
2859
+ if (row === null || col === null) {
2860
+ dead();
2861
+ return;
2862
+ }
2863
+ replace(refStart, emitCell(firstCell, row, col));
2864
+ };
2865
+ while (i < n) {
2866
+ const ch = formula[i];
2867
+ if (ch === '"') {
2868
+ let j = i + 1;
2869
+ while (j < n) {
2870
+ if (formula[j] === '"') {
2871
+ if (formula[j + 1] === '"') {
2872
+ j += 2;
2873
+ continue;
2874
+ }
2875
+ j++;
2876
+ break;
2877
+ }
2878
+ j++;
2879
+ }
2880
+ i = j;
2881
+ continue;
2882
+ }
2883
+ if (ch === "'") {
2884
+ const quoted = readQuoted();
2885
+ if (formula[i] === "!") {
2886
+ i++;
2887
+ const name = quoted.slice(1, -1);
2888
+ const rewritable = name.indexOf(":") === -1 && (name.indexOf("''") === -1 ? name : name.replace(/''/g, "'")).toUpperCase() === editedSheet;
2889
+ consumeRef(i, rewritable);
2890
+ }
2891
+ continue;
2892
+ }
2893
+ if (isWordChar(formula.charCodeAt(i))) {
2894
+ const start = i;
2895
+ const word = readWord();
2896
+ if (formula[i] === "!") {
2897
+ i++;
2898
+ consumeRef(i, word.toUpperCase() === editedSheet);
2899
+ continue;
2900
+ }
2901
+ if (formula[i] === ":") {
2902
+ const save = i;
2903
+ i++;
2904
+ const w2 = i < n && isWordChar(formula.charCodeAt(i)) ? readWord() : "";
2905
+ if (w2 && formula[i] === "!") {
2906
+ i++;
2907
+ consumeRef(i, false);
2908
+ continue;
2909
+ }
2910
+ i = save;
2911
+ }
2912
+ if (formula[i] === "[") {
2913
+ let depth = 0;
2914
+ let j = i;
2915
+ while (j < n) {
2916
+ if (formula[j] === "[") depth++;
2917
+ else if (formula[j] === "]" && --depth === 0) {
2918
+ j++;
2919
+ break;
2920
+ }
2921
+ j++;
2922
+ }
2923
+ i = j;
2924
+ continue;
2925
+ }
2926
+ i = start;
2927
+ consumeRef(start, unqualifiedRewritable);
2928
+ continue;
2929
+ }
2930
+ i++;
2931
+ }
2932
+ if (!changed) return { text: formula, changed: false, hasRefError };
2933
+ return { text: out + formula.slice(flushed), changed: true, hasRefError };
2934
+ }
2935
+
2936
+ // src/model/formula-ref-translate.ts
2937
+ function isWordChar2(code) {
2938
+ return code >= 65 && code <= 90 || // A-Z
2939
+ code >= 97 && code <= 122 || // a-z
2940
+ code >= 48 && code <= 57 || // 0-9
2941
+ code === 95 || // _
2942
+ code === 46 || // .
2943
+ code === 36;
2944
+ }
2945
+ function parseCellComponent2(word) {
2946
+ const m = /^(\$?)([A-Za-z]{1,3})(\$?)(\d+)$/.exec(word);
2947
+ if (!m) return null;
2948
+ const col = columnNumber(m[2]);
2949
+ const row = parseInt(m[4], 10);
2950
+ if (col > EXCEL_MAX_COLUMNS || row < 1 || row > EXCEL_MAX_ROWS) return null;
2951
+ return { colAbs: m[1] === "$", col, rowAbs: m[3] === "$", row };
2952
+ }
2953
+ function parseColComponent2(word) {
2954
+ const m = /^(\$?)([A-Za-z]{1,3})$/.exec(word);
2955
+ if (!m) return null;
2956
+ const col = columnNumber(m[2]);
2957
+ return col > EXCEL_MAX_COLUMNS ? null : { abs: m[1] === "$", col };
2958
+ }
2959
+ function parseRowComponent2(word) {
2960
+ const m = /^(\$?)(\d+)$/.exec(word);
2961
+ if (!m) return null;
2962
+ const row = parseInt(m[2], 10);
2963
+ return row < 1 || row > EXCEL_MAX_ROWS ? null : { abs: m[1] === "$", row };
2964
+ }
2965
+ function move(index, delta, abs, max) {
2966
+ if (abs || delta === 0) return index;
2967
+ const moved = index + delta;
2968
+ return moved < 1 || moved > max ? null : moved;
2969
+ }
2970
+ function translateFormulaRefs(formula, dRow, dCol) {
2971
+ if (dRow === 0 && dCol === 0) return { text: formula, hasRefError: false };
2972
+ const n = formula.length;
2973
+ let out = "";
2974
+ let flushed = 0;
2975
+ let hasRefError = false;
2976
+ let i = 0;
2977
+ const replace = (start, text) => {
2978
+ if (text.length === i - start && formula.startsWith(text, start)) return;
2979
+ out += formula.slice(flushed, start) + text;
2980
+ flushed = i;
2981
+ };
2982
+ const readWord = () => {
2983
+ let j = i;
2984
+ while (j < n && isWordChar2(formula.charCodeAt(j))) j++;
2985
+ const w = formula.slice(i, j);
2986
+ i = j;
2987
+ return w;
2988
+ };
2989
+ const readQuoted = () => {
2990
+ let j = i + 1;
2991
+ while (j < n) {
2992
+ if (formula[j] === "'") {
2993
+ if (formula[j + 1] === "'") {
2994
+ j += 2;
2995
+ continue;
2996
+ }
2997
+ j++;
2998
+ break;
2999
+ }
3000
+ j++;
3001
+ }
3002
+ const q = formula.slice(i, j);
3003
+ i = j;
3004
+ return q;
3005
+ };
3006
+ const emitCell2 = (c, row, col) => `${c.colAbs ? "$" : ""}${columnLetter(col)}${c.rowAbs ? "$" : ""}${row}`;
3007
+ const consumeRef = (refStart, rewrite = true) => {
3008
+ const first = readWord();
3009
+ const firstCell = parseCellComponent2(first);
3010
+ const firstCol = firstCell ? null : parseColComponent2(first);
3011
+ const firstRow = firstCell || firstCol ? null : parseRowComponent2(first);
3012
+ if (!firstCell && !firstCol && !firstRow) return;
3013
+ let secondCell = null;
3014
+ let secondCol = null;
3015
+ let secondRow = null;
3016
+ let isRange = false;
3017
+ if (formula[i] === ":" && i + 1 < n && isWordChar2(formula.charCodeAt(i + 1))) {
3018
+ const save = i;
3019
+ i++;
3020
+ const w = readWord();
3021
+ if (firstCell) secondCell = parseCellComponent2(w);
3022
+ else if (firstCol) secondCol = parseColComponent2(w);
3023
+ else if (firstRow) secondRow = parseRowComponent2(w);
3024
+ if (secondCell || secondCol || secondRow) isRange = true;
3025
+ else i = save;
3026
+ }
3027
+ if (!isRange && formula[i] === "(") return;
3028
+ if (!rewrite) {
3029
+ if (formula[i] === "#") i++;
3030
+ return;
3031
+ }
3032
+ const dead = () => {
3033
+ if (formula[i] === "#") i++;
3034
+ replace(refStart, "#REF!");
3035
+ hasRefError = true;
3036
+ };
3037
+ if (isRange) {
3038
+ if (firstCell && secondCell) {
3039
+ const r1 = move(firstCell.row, dRow, firstCell.rowAbs, EXCEL_MAX_ROWS);
3040
+ const c1 = move(firstCell.col, dCol, firstCell.colAbs, EXCEL_MAX_COLUMNS);
3041
+ const r2 = move(secondCell.row, dRow, secondCell.rowAbs, EXCEL_MAX_ROWS);
3042
+ const c2 = move(secondCell.col, dCol, secondCell.colAbs, EXCEL_MAX_COLUMNS);
3043
+ if (r1 === null || c1 === null || r2 === null || c2 === null) {
3044
+ dead();
3045
+ return;
3046
+ }
3047
+ replace(refStart, `${emitCell2(firstCell, r1, c1)}:${emitCell2(secondCell, r2, c2)}`);
3048
+ return;
3049
+ }
3050
+ if (firstCol && secondCol) {
3051
+ const c1 = move(firstCol.col, dCol, firstCol.abs, EXCEL_MAX_COLUMNS);
3052
+ const c2 = move(secondCol.col, dCol, secondCol.abs, EXCEL_MAX_COLUMNS);
3053
+ if (c1 === null || c2 === null) {
3054
+ dead();
3055
+ return;
3056
+ }
3057
+ replace(refStart, `${firstCol.abs ? "$" : ""}${columnLetter(c1)}:${secondCol.abs ? "$" : ""}${columnLetter(c2)}`);
3058
+ return;
3059
+ }
3060
+ if (firstRow && secondRow) {
3061
+ const r1 = move(firstRow.row, dRow, firstRow.abs, EXCEL_MAX_ROWS);
3062
+ const r2 = move(secondRow.row, dRow, secondRow.abs, EXCEL_MAX_ROWS);
3063
+ if (r1 === null || r2 === null) {
3064
+ dead();
3065
+ return;
3066
+ }
3067
+ replace(refStart, `${firstRow.abs ? "$" : ""}${r1}:${secondRow.abs ? "$" : ""}${r2}`);
3068
+ return;
3069
+ }
3070
+ return;
3071
+ }
3072
+ if (!firstCell) return;
3073
+ const row = move(firstCell.row, dRow, firstCell.rowAbs, EXCEL_MAX_ROWS);
3074
+ const col = move(firstCell.col, dCol, firstCell.colAbs, EXCEL_MAX_COLUMNS);
3075
+ if (row === null || col === null) {
3076
+ dead();
3077
+ return;
3078
+ }
3079
+ replace(refStart, emitCell2(firstCell, row, col));
3080
+ };
3081
+ while (i < n) {
3082
+ const ch = formula[i];
3083
+ if (ch === '"') {
3084
+ let j = i + 1;
3085
+ while (j < n) {
3086
+ if (formula[j] === '"') {
3087
+ if (formula[j + 1] === '"') {
3088
+ j += 2;
3089
+ continue;
3090
+ }
3091
+ j++;
3092
+ break;
3093
+ }
3094
+ j++;
3095
+ }
3096
+ i = j;
3097
+ continue;
3098
+ }
3099
+ if (ch === "'") {
3100
+ readQuoted();
3101
+ if (formula[i] === "!") {
3102
+ i++;
3103
+ consumeRef(i);
3104
+ }
3105
+ continue;
3106
+ }
3107
+ if (isWordChar2(formula.charCodeAt(i))) {
3108
+ const start = i;
3109
+ readWord();
3110
+ if (formula[i] === "!") {
3111
+ i++;
3112
+ consumeRef(i);
3113
+ continue;
3114
+ }
3115
+ if (formula[i] === ":") {
3116
+ const save = i;
3117
+ i++;
3118
+ const w2 = i < n && isWordChar2(formula.charCodeAt(i)) ? readWord() : "";
3119
+ if (w2 && formula[i] === "!") {
3120
+ i++;
3121
+ consumeRef(i, false);
3122
+ continue;
3123
+ }
3124
+ i = save;
3125
+ }
3126
+ if (formula[i] === "[") {
3127
+ let depth = 0;
3128
+ let j = i;
3129
+ while (j < n) {
3130
+ if (formula[j] === "[") depth++;
3131
+ else if (formula[j] === "]" && --depth === 0) {
3132
+ j++;
3133
+ break;
3134
+ }
3135
+ j++;
3136
+ }
3137
+ i = j;
3138
+ continue;
3139
+ }
3140
+ i = start;
3141
+ consumeRef(start);
3142
+ continue;
3143
+ }
3144
+ i++;
3145
+ }
3146
+ return { text: out + formula.slice(flushed), hasRefError };
3147
+ }
3148
+
3149
+ // src/model/range.ts
3150
+ var _sheet, _startRow, _startColumn, _endRow, _endColumn, _address, _Range_instances, forEach_fn, forEachExisting_fn;
3151
+ var Range = class {
3152
+ constructor(sheet, rangeReference) {
3153
+ __privateAdd(this, _Range_instances);
3154
+ __privateAdd(this, _sheet);
3155
+ __privateAdd(this, _startRow);
3156
+ __privateAdd(this, _startColumn);
3157
+ __privateAdd(this, _endRow);
3158
+ __privateAdd(this, _endColumn);
3159
+ __privateAdd(this, _address);
3160
+ __privateSet(this, _sheet, sheet);
3161
+ __privateSet(this, _address, rangeReference.toUpperCase());
3162
+ const { startRow, startColumn, endRow, endColumn } = parseRangeRef(rangeReference);
3163
+ __privateSet(this, _startRow, startRow);
3164
+ __privateSet(this, _startColumn, startColumn);
3165
+ __privateSet(this, _endRow, endRow);
3166
+ __privateSet(this, _endColumn, endColumn);
3167
+ }
3168
+ /** The range address e.g. "A1:B10". */
3169
+ get address() {
3170
+ return __privateGet(this, _address);
3171
+ }
3172
+ /** Number of rows in the range. */
3173
+ get rowCount() {
3174
+ return __privateGet(this, _endRow) - __privateGet(this, _startRow) + 1;
3175
+ }
3176
+ /** Number of columns in the range. */
3177
+ get columnCount() {
3178
+ return __privateGet(this, _endColumn) - __privateGet(this, _startColumn) + 1;
3179
+ }
3180
+ // ── Value bulk operations ──────────────────────────────────────────────────
3181
+ /**
3182
+ * Sets values from a 2D array.
3183
+ * Values that exceed the range boundary are silently ignored.
3184
+ */
3185
+ setValues(values) {
3186
+ for (let r = 0; r < values.length && __privateGet(this, _startRow) + r <= __privateGet(this, _endRow); r++) {
3187
+ const rowValues = values[r];
3188
+ for (let c = 0; c < rowValues.length && __privateGet(this, _startColumn) + c <= __privateGet(this, _endColumn); c++) {
3189
+ const cell = __privateGet(this, _sheet).getCell(__privateGet(this, _startRow) + r, __privateGet(this, _startColumn) + c);
3190
+ const val = rowValues[c];
3191
+ cell.setValue(val);
3192
+ }
3193
+ }
3194
+ }
3195
+ /**
3196
+ * Gets all cell values as a 2D array (row-major order).
3197
+ */
3198
+ getValues() {
3199
+ const rowCount = this.rowCount;
3200
+ const columnCount = this.columnCount;
3201
+ const result = new Array(rowCount);
3202
+ for (let r = 0; r < rowCount; r++) result[r] = new Array(columnCount).fill(null);
3203
+ const startRow = __privateGet(this, _startRow);
3204
+ const startColumn = __privateGet(this, _startColumn);
3205
+ __privateGet(this, _sheet).forEachCellIn(
3206
+ startRow,
3207
+ startColumn,
3208
+ __privateGet(this, _endRow),
3209
+ __privateGet(this, _endColumn),
3210
+ (cell, row, column) => {
3211
+ result[row - startRow][column - startColumn] = cell.getValue();
3212
+ }
3213
+ );
3214
+ return result;
2236
3215
  }
2237
3216
  /** Clears all cells in the range (values only, preserves style). */
2238
3217
  clear() {
2239
- __privateMethod(this, _Range_instances, forEach_fn).call(this, (cell) => cell.clear());
3218
+ __privateMethod(this, _Range_instances, forEachExisting_fn).call(this, (cell) => cell.clear());
2240
3219
  }
2241
- /** Auto-fits all columns in the range. */
2242
- autoFit() {
2243
- for (let col = __privateGet(this, _startColumn); col <= __privateGet(this, _endColumn); col++) {
2244
- __privateGet(this, _sheet).autoFitColumn(col);
3220
+ /** Clears values AND styles of all cells in the range. */
3221
+ clearAll() {
3222
+ __privateMethod(this, _Range_instances, forEachExisting_fn).call(this, (cell) => cell.clearAll());
3223
+ }
3224
+ /**
3225
+ * Moves the whole range so its top-left cell lands on `destTopLeft`,
3226
+ * carrying values, formulas, styles and spill anchors. The vacated cells
3227
+ * are cleared and the destination block is fully replaced (empty source
3228
+ * cells clear their destination). Overlapping source/destination is safe.
3229
+ * Formula text is NOT adjusted for the new position.
3230
+ *
3231
+ * @example
3232
+ * ws.getRange('A1:B3').moveTo('D1');
3233
+ */
3234
+ moveTo(destTopLeft) {
3235
+ const dest = parseCellRef(destTopLeft.replace(/\$/g, "").toUpperCase());
3236
+ const deltaRow = dest.row - __privateGet(this, _startRow);
3237
+ const deltaCol = dest.column - __privateGet(this, _startColumn);
3238
+ if (deltaRow === 0 && deltaCol === 0) return;
3239
+ if (__privateGet(this, _endRow) + deltaRow > EXCEL_MAX_ROWS || __privateGet(this, _endColumn) + deltaCol > EXCEL_MAX_COLUMNS) {
3240
+ throw new RangeError("Destination extends past the sheet limits.");
3241
+ }
3242
+ const rowOrder = deltaRow > 0 ? { from: __privateGet(this, _endRow), to: __privateGet(this, _startRow), step: -1 } : { from: __privateGet(this, _startRow), to: __privateGet(this, _endRow), step: 1 };
3243
+ const colOrder = deltaCol > 0 ? { from: __privateGet(this, _endColumn), to: __privateGet(this, _startColumn), step: -1 } : { from: __privateGet(this, _startColumn), to: __privateGet(this, _endColumn), step: 1 };
3244
+ for (let r = rowOrder.from; deltaRow > 0 ? r >= rowOrder.to : r <= rowOrder.to; r += rowOrder.step) {
3245
+ for (let c = colOrder.from; deltaCol > 0 ? c >= colOrder.to : c <= colOrder.to; c += colOrder.step) {
3246
+ __privateGet(this, _sheet).moveCellRC(r, c, r + deltaRow, c + deltaCol);
3247
+ }
2245
3248
  }
2246
3249
  }
3250
+ /**
3251
+ * Auto-fits all columns in the range. Measures every column in one sparse
3252
+ * sweep rather than re-scanning the sheet per column.
3253
+ */
3254
+ autoFit() {
3255
+ __privateGet(this, _sheet).autoFitColumns(__privateGet(this, _startColumn), __privateGet(this, _endColumn));
3256
+ }
2247
3257
  // ── Style bulk operations ──────────────────────────────────────────────────
2248
3258
  /** Applies a CellStyle to all cells in the range. Returns this for chaining. */
2249
3259
  setStyle(style) {
@@ -2316,9 +3326,57 @@ forEach_fn = function(action) {
2316
3326
  }
2317
3327
  }
2318
3328
  };
3329
+ /**
3330
+ * Like #forEach but only visits cells that already exist — used by the
3331
+ * clearing operations so they never materialise empty cells, and so the cost
3332
+ * tracks the populated cells rather than the area of the range.
3333
+ */
3334
+ forEachExisting_fn = function(action) {
3335
+ __privateGet(this, _sheet).forEachCellIn(
3336
+ __privateGet(this, _startRow),
3337
+ __privateGet(this, _startColumn),
3338
+ __privateGet(this, _endRow),
3339
+ __privateGet(this, _endColumn),
3340
+ (cell) => action(cell)
3341
+ );
3342
+ };
2319
3343
 
2320
3344
  // src/model/worksheet.ts
2321
- var _sharedStrings2, _styles2, _name, _rows, _cols, _mergeCells, _pane, _autoFilter, _dataValidations, _visibility, _drawings, _preservedTail, _preservedRels, _Worksheet_instances, activeTail_fn, buildColsXml_fn, buildSheetDataXml_fn, buildMergeCellsXml_fn, buildDataValidationsXml_fn, buildSheetViewXml_fn, getOrCreateRow_fn;
3345
+ var PROTECTION_DEFAULTS = {
3346
+ allowSelectLockedCells: true,
3347
+ allowSelectUnlockedCells: true,
3348
+ allowFormatCells: false,
3349
+ allowFormatColumns: false,
3350
+ allowFormatRows: false,
3351
+ allowInsertRows: false,
3352
+ allowInsertColumns: false,
3353
+ allowInsertHyperlinks: false,
3354
+ allowDeleteRows: false,
3355
+ allowDeleteColumns: false,
3356
+ allowSort: false,
3357
+ allowAutoFilter: false,
3358
+ allowPivotTables: false,
3359
+ allowEditObjects: false,
3360
+ allowEditScenarios: false
3361
+ };
3362
+ var PROTECTION_ATTRS = [
3363
+ ["allowSelectLockedCells", "selectLockedCells"],
3364
+ ["allowSelectUnlockedCells", "selectUnlockedCells"],
3365
+ ["allowFormatCells", "formatCells"],
3366
+ ["allowFormatColumns", "formatColumns"],
3367
+ ["allowFormatRows", "formatRows"],
3368
+ ["allowInsertRows", "insertRows"],
3369
+ ["allowInsertColumns", "insertColumns"],
3370
+ ["allowInsertHyperlinks", "insertHyperlinks"],
3371
+ ["allowDeleteRows", "deleteRows"],
3372
+ ["allowDeleteColumns", "deleteColumns"],
3373
+ ["allowSort", "sort"],
3374
+ ["allowAutoFilter", "autoFilter"],
3375
+ ["allowPivotTables", "pivotTables"],
3376
+ ["allowEditObjects", "objects"],
3377
+ ["allowEditScenarios", "scenarios"]
3378
+ ];
3379
+ var _sharedStrings2, _styles2, _name, _rows, _cols, _mergeCells, _pane, _autoFilter, _dataValidations, _visibility, _drawings, _protection, _protectionCredentials, _hyperlinks, _comments, _commentsDirty, _pendingHyperlinkRels, _preservedTail, _preservedRels, _version2, _usedBoundsMemo, _xmlMemo, _Worksheet_instances, touch_fn2, findCellData_fn, computeUsedBounds_fn, takeCellData_fn, takeCellDataAt_fn, deleteCellData_fn, applyStructural_fn, shiftRowsAndCells_fn, shiftRows_fn, shiftColumns_fn, maxPopulatedRow_fn, shiftSpillRanges_fn, shiftSpillRange_fn, rewriteFormulas_fn, shiftMerges_fn, shiftAutoFilter_fn, shiftValidations_fn, shiftHyperlinks_fn, shiftComments_fn, shiftColDefs_fn, relIdBase_fn, activeTail_fn, buildTailXml_fn, buildSheetProtectionXml_fn, buildHyperlinksXml_fn, resolveSharedFormulas_fn, buildColsXml_fn, buildSheetDataXml_fn, buildMergeCellsXml_fn, buildDataValidationsXml_fn, buildSheetViewXml_fn, splitColSpan_fn, insertColDef_fn, findColDefIndex_fn, findColDef_fn, pruneColDefs_fn, getOrCreateRow_fn;
2322
3380
  var Worksheet = class {
2323
3381
  /** @internal */
2324
3382
  constructor(name, sharedStrings, styles) {
@@ -2334,16 +3392,52 @@ var Worksheet = class {
2334
3392
  __privateAdd(this, _dataValidations, []);
2335
3393
  __privateAdd(this, _visibility, "visible");
2336
3394
  __privateAdd(this, _drawings, []);
3395
+ __privateAdd(this, _protection);
3396
+ /**
3397
+ * Password/hash attributes of a protection element read from a file, kept
3398
+ * verbatim so a protected sheet round-trips with its password intact. This
3399
+ * library never creates them: see {@link protect}.
3400
+ */
3401
+ __privateAdd(this, _protectionCredentials, "");
3402
+ /** Hyperlinks keyed by their normalised ref, in insertion order. */
3403
+ __privateAdd(this, _hyperlinks, /* @__PURE__ */ new Map());
3404
+ /** Comments keyed by their normalised cell ref, in insertion order. */
3405
+ __privateAdd(this, _comments, /* @__PURE__ */ new Map());
3406
+ /**
3407
+ * True once the comment set has been modified through the API. Until then a
3408
+ * sheet loaded from a file keeps its original comments and VML parts verbatim,
3409
+ * so untouched files round-trip byte-for-byte; from then on this sheet owns
3410
+ * them and regenerates both. See {@link setComment}.
3411
+ */
3412
+ __privateAdd(this, _commentsDirty, false);
3413
+ /** Hyperlink ref → relationship id, awaiting resolution against the rels part. */
3414
+ __privateAdd(this, _pendingHyperlinkRels, /* @__PURE__ */ new Map());
2337
3415
  // Round-trip state captured when this sheet was loaded from a file: the
2338
3416
  // relationship-bearing leaf elements of the worksheet part, and the sheet's
2339
3417
  // original relationships. Re-emitted on save so drawings, images and legacy
2340
3418
  // comment anchors survive open→save. See io/preserved-parts.ts.
2341
3419
  __privateAdd(this, _preservedTail, []);
2342
3420
  __privateAdd(this, _preservedRels, []);
3421
+ /**
3422
+ * Mutation counter for this sheet, shared with every {@link Cell} view it
3423
+ * hands out (a Cell writes directly into the stored CellData). Everything
3424
+ * derived from the sheet's contents — the used-range memo, the cached
3425
+ * worksheet XML — is keyed on it, so every mutator must bump it. The test
3426
+ * `worksheet-version.test.ts` walks the public surface to keep that true.
3427
+ */
3428
+ __privateAdd(this, _version2, { v: 0 });
3429
+ /** Memoised {@link usedBounds}, valid while `#version.v` is unchanged. */
3430
+ __privateAdd(this, _usedBoundsMemo);
3431
+ /** Memoised {@link buildXml} output, valid while `#version.v` is unchanged. */
3432
+ __privateAdd(this, _xmlMemo);
2343
3433
  __privateSet(this, _name, name);
2344
3434
  __privateSet(this, _sharedStrings2, sharedStrings);
2345
3435
  __privateSet(this, _styles2, styles);
2346
3436
  }
3437
+ /** @internal Current mutation count — see {@link #version}. */
3438
+ get version() {
3439
+ return __privateGet(this, _version2).v;
3440
+ }
2347
3441
  // ── Identity ───────────────────────────────────────────────────────────────
2348
3442
  get name() {
2349
3443
  return __privateGet(this, _name);
@@ -2353,24 +3447,39 @@ var Worksheet = class {
2353
3447
  __privateSet(this, _name, value);
2354
3448
  }
2355
3449
  getCell(refOrRow, column) {
2356
- const ref = typeof refOrRow === "string" ? refOrRow.replace(/\$/g, "").toUpperCase() : cellRefFromRowCol(refOrRow, column);
2357
- const { row } = parseCellRef(ref);
3450
+ let ref;
3451
+ let row;
3452
+ if (typeof refOrRow === "string") {
3453
+ ref = normalizeRefFast(refOrRow);
3454
+ row = parseCellRef(ref).row;
3455
+ } else {
3456
+ ref = cellRefFromRowCol(refOrRow, column);
3457
+ row = refOrRow;
3458
+ }
2358
3459
  const rowData = __privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, row);
2359
3460
  let cellData = rowData.cells.get(ref);
2360
3461
  if (!cellData) {
3462
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
2361
3463
  cellData = { reference: ref, styleIndex: 0 };
2362
3464
  rowData.cells.set(ref, cellData);
2363
3465
  }
2364
- return new Cell(cellData, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2));
3466
+ return new Cell(cellData, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2), __privateGet(this, _version2));
2365
3467
  }
2366
3468
  tryGetCell(refOrRow, column) {
2367
- const ref = typeof refOrRow === "string" ? refOrRow.replace(/\$/g, "").toUpperCase() : cellRefFromRowCol(refOrRow, column);
2368
- const { row } = parseCellRef(ref);
2369
- const rowData = __privateGet(this, _rows).get(row);
2370
- if (!rowData) return null;
3469
+ let rowData;
3470
+ let ref;
3471
+ if (typeof refOrRow === "string") {
3472
+ ref = normalizeRefFast(refOrRow);
3473
+ rowData = __privateGet(this, _rows).get(parseCellRef(ref).row);
3474
+ if (!rowData) return null;
3475
+ } else {
3476
+ rowData = __privateGet(this, _rows).get(refOrRow);
3477
+ if (!rowData) return null;
3478
+ ref = cellRefFromRowCol(refOrRow, column);
3479
+ }
2371
3480
  const cellData = rowData.cells.get(ref);
2372
3481
  if (!cellData) return null;
2373
- return new Cell(cellData, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2));
3482
+ return new Cell(cellData, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2), __privateGet(this, _version2));
2374
3483
  }
2375
3484
  cell(refOrRow, column) {
2376
3485
  return typeof refOrRow === "string" ? this.getCell(refOrRow) : this.getCell(refOrRow, column);
@@ -2382,42 +3491,158 @@ var Worksheet = class {
2382
3491
  }
2383
3492
  /** Gets the raw value of a cell (for formula evaluation). */
2384
3493
  getCellValue(reference) {
2385
- return this.tryGetCell(reference)?.getValue() ?? null;
3494
+ const cd = __privateMethod(this, _Worksheet_instances, findCellData_fn).call(this, reference);
3495
+ return cd ? readCellValue(cd, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2)) : null;
3496
+ }
3497
+ /**
3498
+ * Value of a cell addressed by 1-based row/column. The reference-string
3499
+ * overload has to build (and the callee re-parse) a string per cell; a range
3500
+ * read walking a rectangle already knows the coordinates, and on a real
3501
+ * workbook that is tens of millions of strings per recalculation.
3502
+ */
3503
+ getCellValueAt(row, column) {
3504
+ const rowData = __privateGet(this, _rows).get(row);
3505
+ if (!rowData) return null;
3506
+ const cd = rowData.cells.get(cellRefFromRowCol(row, column));
3507
+ return cd ? readCellValue(cd, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2)) : null;
2386
3508
  }
2387
3509
  /** Formula text (without leading '=') of a cell, or null if it has none. */
2388
3510
  getCellFormula(reference) {
2389
- return this.tryGetCell(reference)?.getFormula() ?? null;
3511
+ return __privateMethod(this, _Worksheet_instances, findCellData_fn).call(this, reference)?.formula ?? null;
2390
3512
  }
2391
3513
  /** Spill range (e.g. "A1:A3") of a dynamic-array anchor, or null. Supports A1#. */
2392
3514
  getSpillRange(reference) {
2393
- return this.tryGetCell(reference)?.getArrayRef() ?? null;
3515
+ return __privateMethod(this, _Worksheet_instances, findCellData_fn).call(this, reference)?.arrayRef ?? null;
3516
+ }
3517
+ /**
3518
+ * Iterates the populated rows of the sheet in ascending row order, yielding
3519
+ * each row's 1-based index and its cells (left to right). Rows and cells
3520
+ * that were never written are skipped, so a sparse sheet iterates in
3521
+ * O(populated cells) regardless of its bounds.
3522
+ *
3523
+ * @example
3524
+ * for (const { rowIndex, cells } of ws.rows()) {
3525
+ * for (const cell of cells) console.log(rowIndex, cell.reference, cell.getValue());
3526
+ * }
3527
+ */
3528
+ *rows() {
3529
+ const populated = [...__privateGet(this, _rows).values()].filter((row) => row.cells.size > 0).sort((a, b) => a.rowIndex - b.rowIndex);
3530
+ for (const row of populated) {
3531
+ const cells = [...row.cells.values()].map((cd) => ({ cd, col: columnFromRef(cd.reference, refSplit(cd.reference)) })).sort((a, b) => a.col - b.col).map(({ cd }) => new Cell(cd, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2), __privateGet(this, _version2)));
3532
+ yield { rowIndex: row.rowIndex, cells };
3533
+ }
3534
+ }
3535
+ /**
3536
+ * Iterates every populated cell of the sheet in row-major order. Sparse
3537
+ * counterpart to scanning {@link usedBounds} with {@link tryGetCell}.
3538
+ *
3539
+ * @example
3540
+ * for (const cell of ws.cells()) console.log(cell.reference, cell.getValue());
3541
+ */
3542
+ *cells() {
3543
+ for (const { cells } of this.rows()) yield* cells;
3544
+ }
3545
+ /**
3546
+ * @internal Visits the populated cells inside a rectangle, passing the
3547
+ * coordinates it already knows so the callback never has to parse a
3548
+ * reference. Backs the Range operations that must not create cells; probing
3549
+ * every coordinate would cost O(area) even on an empty sheet.
3550
+ */
3551
+ forEachCellIn(startRow, startColumn, endRow, endColumn, action) {
3552
+ const visit = (row) => {
3553
+ for (const cd of row.cells.values()) {
3554
+ const col = columnFromRef(cd.reference, refSplit(cd.reference));
3555
+ if (col < startColumn || col > endColumn) continue;
3556
+ action(new Cell(cd, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2), __privateGet(this, _version2)), row.rowIndex, col);
3557
+ }
3558
+ };
3559
+ if (endRow - startRow + 1 <= __privateGet(this, _rows).size) {
3560
+ for (let r = startRow; r <= endRow; r++) {
3561
+ const row = __privateGet(this, _rows).get(r);
3562
+ if (row) visit(row);
3563
+ }
3564
+ return;
3565
+ }
3566
+ for (const row of __privateGet(this, _rows).values()) {
3567
+ if (row.rowIndex >= startRow && row.rowIndex <= endRow) visit(row);
3568
+ }
3569
+ }
3570
+ /**
3571
+ * @internal Moves a cell by coordinates, skipping the reference parsing that
3572
+ * the public {@link moveCell} has to do. Used by bulk range moves.
3573
+ */
3574
+ moveCellRC(fromRow, fromColumn, toRow, toColumn) {
3575
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
3576
+ if (fromRow === toRow && fromColumn === toColumn) return;
3577
+ const fromRef = cellRefFromRowCol(fromRow, fromColumn);
3578
+ const toRef = cellRefFromRowCol(toRow, toColumn);
3579
+ const src = __privateMethod(this, _Worksheet_instances, takeCellDataAt_fn).call(this, fromRow, fromRef);
3580
+ if (!src) {
3581
+ __privateMethod(this, _Worksheet_instances, takeCellDataAt_fn).call(this, toRow, toRef);
3582
+ return;
3583
+ }
3584
+ const moved = { ...src, reference: toRef };
3585
+ if (moved.arrayRef) {
3586
+ moved.arrayRef = translateRangeRef(moved.arrayRef, toRow - fromRow, toColumn - fromColumn);
3587
+ }
3588
+ __privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, toRow).cells.set(toRef, moved);
2394
3589
  }
2395
3590
  /**
2396
3591
  * Returns the 1-based extent of populated cells (largest row and column that
2397
3592
  * contain data). Returns zeros for an empty sheet. Useful for snapshotting a
2398
- * sheet into a dense 2D array.
3593
+ * sheet into a dense 2D array — though {@link rows} and {@link cells} are
3594
+ * cheaper when a dense shape is not actually required.
2399
3595
  */
2400
3596
  get usedBounds() {
2401
- let rowCount = 0;
2402
- let columnCount = 0;
2403
- for (const row of __privateGet(this, _rows).values()) {
2404
- if (row.cells.size === 0) continue;
2405
- if (row.rowIndex > rowCount) rowCount = row.rowIndex;
2406
- for (const ref of row.cells.keys()) {
2407
- const col = parseCellRef(ref).column;
2408
- if (col > columnCount) columnCount = col;
2409
- }
2410
- }
2411
- return { rowCount, columnCount };
3597
+ const memo = __privateGet(this, _usedBoundsMemo);
3598
+ if (memo && memo.at === __privateGet(this, _version2).v) return memo.value;
3599
+ const value = __privateMethod(this, _Worksheet_instances, computeUsedBounds_fn).call(this);
3600
+ __privateSet(this, _usedBoundsMemo, { at: __privateGet(this, _version2).v, value });
3601
+ return value;
2412
3602
  }
2413
3603
  // ── Layout ─────────────────────────────────────────────────────────────────
2414
3604
  /** Sets the width of a column (1-based index). */
2415
3605
  setColumnWidth(columnIndex, width) {
3606
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
2416
3607
  if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
2417
3608
  if (width <= 0) throw new RangeError("Width must be > 0.");
2418
- __privateSet(this, _cols, __privateGet(this, _cols).filter((c) => !(c.min <= columnIndex && c.max >= columnIndex)));
2419
- __privateGet(this, _cols).push({ min: columnIndex, max: columnIndex, width, customWidth: true });
2420
- __privateGet(this, _cols).sort((a, b) => a.min - b.min);
3609
+ const def = __privateMethod(this, _Worksheet_instances, splitColSpan_fn).call(this, columnIndex) ?? __privateMethod(this, _Worksheet_instances, insertColDef_fn).call(this, { min: columnIndex, max: columnIndex, customWidth: false });
3610
+ def.width = width;
3611
+ def.customWidth = true;
3612
+ }
3613
+ /**
3614
+ * Removes the explicit width of a column so it falls back to the sheet
3615
+ * default. Counterpart to {@link setColumnWidth}; afterwards
3616
+ * {@link getColumnWidth} returns `undefined`. A hidden column stays hidden.
3617
+ */
3618
+ resetColumnWidth(columnIndex) {
3619
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
3620
+ if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
3621
+ const def = __privateMethod(this, _Worksheet_instances, splitColSpan_fn).call(this, columnIndex);
3622
+ if (!def) return;
3623
+ def.width = void 0;
3624
+ def.customWidth = false;
3625
+ __privateMethod(this, _Worksheet_instances, pruneColDefs_fn).call(this);
3626
+ }
3627
+ /**
3628
+ * Shows or hides a column (1-based index). Hidden columns round-trip to the
3629
+ * file as `<col hidden="1">` and keep any explicit width.
3630
+ */
3631
+ setColumnHidden(columnIndex, hidden = true) {
3632
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
3633
+ if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
3634
+ const def = __privateMethod(this, _Worksheet_instances, splitColSpan_fn).call(this, columnIndex);
3635
+ if (def) {
3636
+ def.hidden = hidden;
3637
+ __privateMethod(this, _Worksheet_instances, pruneColDefs_fn).call(this);
3638
+ } else if (hidden) {
3639
+ __privateMethod(this, _Worksheet_instances, insertColDef_fn).call(this, { min: columnIndex, max: columnIndex, customWidth: false, hidden: true });
3640
+ }
3641
+ }
3642
+ /** True if the given 1-based column is hidden. */
3643
+ isColumnHidden(columnIndex) {
3644
+ if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
3645
+ return __privateMethod(this, _Worksheet_instances, findColDef_fn).call(this, columnIndex)?.hidden === true;
2421
3646
  }
2422
3647
  /**
2423
3648
  * Returns the explicit width of a column (1-based index), or `undefined` when
@@ -2427,25 +3652,34 @@ var Worksheet = class {
2427
3652
  */
2428
3653
  getColumnWidth(columnIndex) {
2429
3654
  if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
2430
- return __privateGet(this, _cols).find((c) => c.min <= columnIndex && c.max >= columnIndex)?.width;
3655
+ return __privateMethod(this, _Worksheet_instances, findColDef_fn).call(this, columnIndex)?.width;
2431
3656
  }
2432
3657
  /** Auto-fits column width based on content (approximate). */
2433
3658
  autoFitColumn(columnIndex) {
2434
- if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
2435
- let maxLen = 8;
2436
- for (const row of __privateGet(this, _rows).values()) {
2437
- const ref = cellRefFromRowCol(row.rowIndex, columnIndex);
2438
- const cellData = row.cells.get(ref);
2439
- if (cellData) {
2440
- const cell = new Cell(cellData, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2));
2441
- const textLen = cell.getText().length;
2442
- if (textLen > maxLen) maxLen = textLen;
2443
- }
3659
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
3660
+ this.autoFitColumns(columnIndex, columnIndex);
3661
+ }
3662
+ /**
3663
+ * Auto-fits an inclusive span of columns (1-based) in a single pass over the
3664
+ * populated cells, instead of re-scanning the sheet once per column.
3665
+ */
3666
+ autoFitColumns(firstColumn, lastColumn) {
3667
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
3668
+ if (firstColumn < 1) throw new RangeError("Column index must be >= 1.");
3669
+ if (lastColumn < firstColumn) return;
3670
+ const maxLen = /* @__PURE__ */ new Map();
3671
+ this.forEachCellIn(1, firstColumn, EXCEL_MAX_ROWS, lastColumn, (cell, _row, column) => {
3672
+ const len = cell.getText().length;
3673
+ if (len > (maxLen.get(column) ?? 0)) maxLen.set(column, len);
3674
+ });
3675
+ for (let col = firstColumn; col <= lastColumn; col++) {
3676
+ const len = Math.max(maxLen.get(col) ?? 0, 8);
3677
+ this.setColumnWidth(col, Math.min(len * 1.2 + 2, 255));
2444
3678
  }
2445
- this.setColumnWidth(columnIndex, Math.min(maxLen * 1.2 + 2, 255));
2446
3679
  }
2447
3680
  /** Sets the height of a row (1-based index). */
2448
3681
  setRowHeight(rowIndex, height) {
3682
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
2449
3683
  if (rowIndex < 1) throw new RangeError("Row index must be >= 1.");
2450
3684
  if (height <= 0) throw new RangeError("Height must be > 0.");
2451
3685
  const row = __privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, rowIndex);
@@ -2460,8 +3694,23 @@ var Worksheet = class {
2460
3694
  if (rowIndex < 1) throw new RangeError("Row index must be >= 1.");
2461
3695
  return __privateGet(this, _rows).get(rowIndex)?.height;
2462
3696
  }
3697
+ /**
3698
+ * Removes the explicit height of a row so it falls back to the sheet
3699
+ * default. Counterpart to {@link setRowHeight}; afterwards
3700
+ * {@link getRowHeight} returns `undefined`. A hidden row stays hidden.
3701
+ */
3702
+ resetRowHeight(rowIndex) {
3703
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
3704
+ if (rowIndex < 1) throw new RangeError("Row index must be >= 1.");
3705
+ const row = __privateGet(this, _rows).get(rowIndex);
3706
+ if (!row) return;
3707
+ row.height = void 0;
3708
+ row.customHeight = void 0;
3709
+ if (row.cells.size === 0 && !row.hidden) __privateGet(this, _rows).delete(rowIndex);
3710
+ }
2463
3711
  /** Shows or hides a row (1-based index). Hidden rows affect SUBTOTAL(101-111). */
2464
3712
  setRowHidden(rowIndex, hidden = true) {
3713
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
2465
3714
  if (rowIndex < 1) throw new RangeError("Row index must be >= 1.");
2466
3715
  __privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, rowIndex).hidden = hidden;
2467
3716
  }
@@ -2469,13 +3718,153 @@ var Worksheet = class {
2469
3718
  isRowHidden(rowIndex) {
2470
3719
  return __privateGet(this, _rows).get(rowIndex)?.hidden === true;
2471
3720
  }
3721
+ /**
3722
+ * The 1-based indices of every hidden row, ascending. Sparse counterpart to
3723
+ * probing {@link isRowHidden} across {@link usedBounds}.
3724
+ */
3725
+ get hiddenRows() {
3726
+ const result = [];
3727
+ for (const row of __privateGet(this, _rows).values()) {
3728
+ if (row.hidden) result.push(row.rowIndex);
3729
+ }
3730
+ return result.sort((a, b) => a - b);
3731
+ }
3732
+ /**
3733
+ * The 1-based indices of every hidden column, ascending. Counterpart to
3734
+ * {@link isColumnHidden}, in the same spirit as {@link hiddenRows}.
3735
+ */
3736
+ get hiddenColumns() {
3737
+ const result = [];
3738
+ for (const def of __privateGet(this, _cols)) {
3739
+ if (!def.hidden) continue;
3740
+ for (let c = def.min; c <= def.max; c++) result.push(c);
3741
+ }
3742
+ return result.sort((a, b) => a - b);
3743
+ }
3744
+ // ── Structural editing ─────────────────────────────────────────────────────
3745
+ /**
3746
+ * Inserts `count` empty rows starting at row `at` (1-based). Everything at
3747
+ * or below `at` moves down: cell values, formulas (references are rewritten
3748
+ * the way Excel does), styles, row heights, hidden flags, merges,
3749
+ * autofilter and data-validation ranges.
3750
+ *
3751
+ * Frozen panes are a view property and stay put. Drawing/chart anchors
3752
+ * preserved from a loaded file are opaque XML and do NOT shift — images
3753
+ * keep their absolute position (known limitation).
3754
+ *
3755
+ * When formulas on OTHER sheets reference this sheet, use the
3756
+ * Workbook-level counterpart so they are rewritten too, or call
3757
+ * `applyExternalShift` on each sheet yourself. If a RecalcEngine is
3758
+ * attached, notify it afterwards (e.g. `engine.onRowsInserted(...)`).
3759
+ *
3760
+ * @example
3761
+ * ws.insertRows(3, 2); // pushes row 3 and below down by two rows
3762
+ */
3763
+ insertRows(at, count = 1) {
3764
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
3765
+ return __privateMethod(this, _Worksheet_instances, applyStructural_fn).call(this, "row", "insert", at, count);
3766
+ }
3767
+ /**
3768
+ * Deletes `count` rows starting at row `at` (1-based). Rows below move up;
3769
+ * formulas referencing the deleted band get `#REF!` (fully inside) or
3770
+ * shrink (partially inside), exactly like Excel. See {@link insertRows}
3771
+ * for the shifting rules and known limitations.
3772
+ */
3773
+ deleteRows(at, count = 1) {
3774
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
3775
+ return __privateMethod(this, _Worksheet_instances, applyStructural_fn).call(this, "row", "delete", at, count);
3776
+ }
3777
+ /**
3778
+ * Inserts `count` empty columns starting at column `at` (1-based).
3779
+ * Column-level counterpart to {@link insertRows}; widths and hidden flags
3780
+ * shift with the columns.
3781
+ */
3782
+ insertColumns(at, count = 1) {
3783
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
3784
+ return __privateMethod(this, _Worksheet_instances, applyStructural_fn).call(this, "col", "insert", at, count);
3785
+ }
3786
+ /**
3787
+ * Deletes `count` columns starting at column `at` (1-based).
3788
+ * Column-level counterpart to {@link deleteRows}.
3789
+ */
3790
+ deleteColumns(at, count = 1) {
3791
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
3792
+ return __privateMethod(this, _Worksheet_instances, applyStructural_fn).call(this, "col", "delete", at, count);
3793
+ }
3794
+ /**
3795
+ * @internal Rewrites formulas on THIS sheet after a structural edit on a
3796
+ * DIFFERENT sheet (named by `shift.sheetName`), so qualified references
3797
+ * like `'Data'!A5` stay correct. Cell positions here do not change.
3798
+ */
3799
+ applyExternalShift(shift) {
3800
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
3801
+ return { refErrors: __privateMethod(this, _Worksheet_instances, rewriteFormulas_fn).call(this, shift) };
3802
+ }
3803
+ /**
3804
+ * Moves a cell's full contents — value, formula, style and spill anchor —
3805
+ * to another cell, overwriting the destination and clearing the source.
3806
+ * A missing source clears the destination. This is a literal move: formula
3807
+ * text is NOT adjusted for the new position.
3808
+ *
3809
+ * @example
3810
+ * ws.moveCell('A1', 'C5');
3811
+ */
3812
+ moveCell(from, to) {
3813
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
3814
+ const fromRef = normalizeRef(from);
3815
+ const toRef = normalizeRef(to);
3816
+ if (fromRef === toRef) return;
3817
+ const src = __privateMethod(this, _Worksheet_instances, takeCellData_fn).call(this, fromRef);
3818
+ if (!src) {
3819
+ __privateMethod(this, _Worksheet_instances, deleteCellData_fn).call(this, toRef);
3820
+ return;
3821
+ }
3822
+ const fromPos = parseCellRef(fromRef);
3823
+ const toPos = parseCellRef(toRef);
3824
+ const moved = { ...src, reference: toRef };
3825
+ if (moved.arrayRef) {
3826
+ moved.arrayRef = translateRangeRef(
3827
+ moved.arrayRef,
3828
+ toPos.row - fromPos.row,
3829
+ toPos.column - fromPos.column
3830
+ );
3831
+ }
3832
+ __privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, toPos.row).cells.set(toRef, moved);
3833
+ }
3834
+ /**
3835
+ * Copies a cell's value, formula and style to another cell, overwriting the
3836
+ * destination and leaving the source untouched. A missing source clears the
3837
+ * destination. This is a literal copy: formula references are NOT adjusted,
3838
+ * and the spill anchor (`arrayRef`) is not copied — a recalc engine
3839
+ * re-establishes it.
3840
+ *
3841
+ * @example
3842
+ * ws.copyCell('A1', 'C5');
3843
+ */
3844
+ copyCell(from, to) {
3845
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
3846
+ const fromRef = normalizeRef(from);
3847
+ const toRef = normalizeRef(to);
3848
+ if (fromRef === toRef) return;
3849
+ const srcRow = __privateGet(this, _rows).get(parseCellRef(fromRef).row);
3850
+ const src = srcRow?.cells.get(fromRef);
3851
+ if (!src) {
3852
+ __privateMethod(this, _Worksheet_instances, deleteCellData_fn).call(this, toRef);
3853
+ return;
3854
+ }
3855
+ const toPos = parseCellRef(toRef);
3856
+ const copied = { ...src, reference: toRef, arrayRef: void 0 };
3857
+ __privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, toPos.row).cells.set(toRef, copied);
3858
+ }
2472
3859
  /** Merges cells in the given range (e.g. "A1:B2"). */
2473
3860
  mergeCells(rangeReference) {
3861
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
2474
3862
  if (!rangeReference) throw new Error("Range reference cannot be empty.");
2475
3863
  __privateGet(this, _mergeCells).push({ ref: rangeReference });
2476
3864
  }
2477
3865
  /** Removes a merge for the given range reference. */
2478
3866
  unmergeCells(rangeReference) {
3867
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
2479
3868
  __privateSet(this, _mergeCells, __privateGet(this, _mergeCells).filter((m) => m.ref !== rangeReference));
2480
3869
  }
2481
3870
  /**
@@ -2492,12 +3881,14 @@ var Worksheet = class {
2492
3881
  * @param column Number of columns to freeze (0 = none)
2493
3882
  */
2494
3883
  freezePanes(row, column) {
3884
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
2495
3885
  if (row < 0) throw new RangeError("Row must be >= 0.");
2496
3886
  if (column < 0) throw new RangeError("Column must be >= 0.");
2497
3887
  __privateSet(this, _pane, row === 0 && column === 0 ? void 0 : { row, column });
2498
3888
  }
2499
3889
  /** Sets auto-filter on the given range. */
2500
3890
  setAutoFilter(rangeReference) {
3891
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
2501
3892
  __privateSet(this, _autoFilter, { ref: rangeReference });
2502
3893
  }
2503
3894
  /**
@@ -2506,15 +3897,136 @@ var Worksheet = class {
2506
3897
  * @param listFormula Formula for the dropdown list (e.g. "'Sheet2'!$A$1:$A$10")
2507
3898
  */
2508
3899
  addDropdownValidation(cellRange, listFormula) {
3900
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
2509
3901
  __privateGet(this, _dataValidations).push({ sqref: cellRange, formula: listFormula });
2510
3902
  }
2511
3903
  /** Sets the visibility state of this worksheet. */
2512
3904
  setVisibility(state) {
3905
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
2513
3906
  __privateSet(this, _visibility, state);
2514
3907
  }
2515
3908
  get visibility() {
2516
3909
  return __privateGet(this, _visibility);
2517
3910
  }
3911
+ // ── Sheet protection ───────────────────────────────────────────────────────
3912
+ /**
3913
+ * Turns on sheet protection, optionally relaxing individual permissions.
3914
+ * Cells are locked by default in Excel, so protecting a sheet makes all of
3915
+ * them read-only; clear a cell style's `locked` flag to leave a cell editable.
3916
+ *
3917
+ * Excel's sheet protection is a UI guard, **not** security: the contents stay
3918
+ * readable to anything that opens the file. This library therefore never
3919
+ * creates a protection password. A password read from an existing file is
3920
+ * preserved so the sheet round-trips unchanged.
3921
+ *
3922
+ * @example
3923
+ * ws.protect(); // lock everything
3924
+ * ws.protect({ allowSort: true, allowAutoFilter: true });
3925
+ */
3926
+ protect(options) {
3927
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
3928
+ __privateSet(this, _protection, { ...PROTECTION_DEFAULTS, ...options });
3929
+ }
3930
+ /** Turns off sheet protection, discarding any password read from a file. */
3931
+ unprotect() {
3932
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
3933
+ __privateSet(this, _protection, void 0);
3934
+ __privateSet(this, _protectionCredentials, "");
3935
+ }
3936
+ /** True when this sheet is protected. */
3937
+ get isProtected() {
3938
+ return __privateGet(this, _protection) !== void 0;
3939
+ }
3940
+ /** The active permissions, or `undefined` when the sheet is not protected. */
3941
+ get protection() {
3942
+ return __privateGet(this, _protection) ? { ...__privateGet(this, _protection) } : void 0;
3943
+ }
3944
+ // ── Hyperlinks ─────────────────────────────────────────────────────────────
3945
+ /**
3946
+ * Attaches a hyperlink to a cell or range. Give `target` for an external
3947
+ * destination or `location` for one inside the workbook; passing both keeps
3948
+ * the external target and uses the location as its fragment, which is how
3949
+ * Excel links to an anchor within a document.
3950
+ *
3951
+ * A cell's displayed text still comes from its own value — set that
3952
+ * separately; `display` only overrides what Excel shows in the edit bar.
3953
+ *
3954
+ * @example
3955
+ * ws.getCell('A1').setValue('Anthropic');
3956
+ * ws.setHyperlink('A1', { target: 'https://anthropic.com', tooltip: 'Open site' });
3957
+ * ws.setHyperlink('B1', { location: "'Sheet2'!A1" });
3958
+ */
3959
+ setHyperlink(ref, link) {
3960
+ if (!link.target && !link.location) {
3961
+ throw new Error("A hyperlink needs a target (external) or a location (in-workbook).");
3962
+ }
3963
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
3964
+ const normalized = normalizeRangeRef(ref);
3965
+ __privateGet(this, _hyperlinks).set(normalized, { ref: normalized, ...link });
3966
+ }
3967
+ /** The hyperlink covering exactly this ref, or undefined. */
3968
+ getHyperlink(ref) {
3969
+ return __privateGet(this, _hyperlinks).get(normalizeRangeRef(ref));
3970
+ }
3971
+ /** Removes the hyperlink on this ref. Returns true if one was there. */
3972
+ removeHyperlink(ref) {
3973
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
3974
+ return __privateGet(this, _hyperlinks).delete(normalizeRangeRef(ref));
3975
+ }
3976
+ /** Every hyperlink on the sheet, including ones read from a file. */
3977
+ get hyperlinks() {
3978
+ return [...__privateGet(this, _hyperlinks).values()];
3979
+ }
3980
+ // ── Comments (notes) ───────────────────────────────────────────────────────
3981
+ /**
3982
+ * Attaches a comment (a "note" in current Excel versions) to a cell,
3983
+ * replacing any comment already there.
3984
+ *
3985
+ * Note that writing comments makes this sheet regenerate its comment and
3986
+ * legacy-drawing parts on save. If the file was opened with other legacy
3987
+ * drawing content on the same sheet — form controls, for instance — that
3988
+ * content is not reproduced. Reading comments has no such effect.
3989
+ *
3990
+ * @example
3991
+ * ws.setComment('B4', 'Check this figure', { author: 'Bill' });
3992
+ */
3993
+ setComment(ref, text, options) {
3994
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
3995
+ const normalized = normalizeRef(ref);
3996
+ __privateGet(this, _comments).set(normalized, {
3997
+ ref: normalized,
3998
+ text,
3999
+ author: options?.author ?? ""
4000
+ });
4001
+ __privateSet(this, _commentsDirty, true);
4002
+ }
4003
+ /** The comment on a cell, or undefined. */
4004
+ getComment(ref) {
4005
+ return __privateGet(this, _comments).get(normalizeRef(ref));
4006
+ }
4007
+ /** Removes the comment on a cell. Returns true if one was there. */
4008
+ removeComment(ref) {
4009
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
4010
+ const removed = __privateGet(this, _comments).delete(normalizeRef(ref));
4011
+ if (removed) __privateSet(this, _commentsDirty, true);
4012
+ return removed;
4013
+ }
4014
+ /** Every comment on the sheet, including ones read from a file. */
4015
+ get comments() {
4016
+ return [...__privateGet(this, _comments).values()];
4017
+ }
4018
+ /** @internal True when this sheet must write its own comment parts. */
4019
+ get ownsCommentParts() {
4020
+ return __privateGet(this, _commentsDirty) && __privateGet(this, _comments).size > 0;
4021
+ }
4022
+ /**
4023
+ * @internal True when the sheet took over its comment parts and any preserved
4024
+ * originals must be dropped — including the case where every comment was
4025
+ * deleted and nothing replaces them.
4026
+ */
4027
+ get commentPartsReplaced() {
4028
+ return __privateGet(this, _commentsDirty);
4029
+ }
2518
4030
  // ── Drawings (charts, images …) ──────────────────────────────────────────────
2519
4031
  /**
2520
4032
  * Attaches a drawing provider (e.g. a chart) to this worksheet.
@@ -2522,6 +4034,7 @@ var Worksheet = class {
2522
4034
  * save pipeline turns registered providers into valid OOXML drawing/chart parts.
2523
4035
  */
2524
4036
  addDrawingProvider(provider) {
4037
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
2525
4038
  __privateGet(this, _drawings).push(provider);
2526
4039
  }
2527
4040
  /**
@@ -2533,6 +4046,7 @@ var Worksheet = class {
2533
4046
  * deliberately drop a chart that came from the source file.
2534
4047
  */
2535
4048
  clearDrawings() {
4049
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
2536
4050
  __privateSet(this, _drawings, []);
2537
4051
  __privateSet(this, _preservedTail, __privateGet(this, _preservedTail).filter((t) => t.tag !== "drawing"));
2538
4052
  }
@@ -2552,7 +4066,12 @@ var Worksheet = class {
2552
4066
  */
2553
4067
  get preservedRelationships() {
2554
4068
  const liveIds = new Set(__privateMethod(this, _Worksheet_instances, activeTail_fn).call(this).map((t) => t.relId));
2555
- return __privateGet(this, _preservedRels).filter((r) => liveIds.has(r.id));
4069
+ return __privateGet(this, _preservedRels).filter((r) => {
4070
+ if (r.type === REL_TYPES.comments) {
4071
+ return !this.commentPartsReplaced && __privateGet(this, _comments).size > 0;
4072
+ }
4073
+ return liveIds.has(r.id);
4074
+ });
2556
4075
  }
2557
4076
  /**
2558
4077
  * @internal Package paths of relationships that were preserved at load time
@@ -2569,55 +4088,119 @@ var Worksheet = class {
2569
4088
  * clear every preserved id so the two sets cannot collide.
2570
4089
  */
2571
4090
  get drawingRelId() {
2572
- let max = 0;
2573
- for (const r of __privateGet(this, _preservedRels)) {
2574
- const n = /^rId(\d+)$/.exec(r.id);
2575
- if (n) max = Math.max(max, parseInt(n[1], 10));
4091
+ return this.generatedRels().drawing ?? `rId${__privateMethod(this, _Worksheet_instances, relIdBase_fn).call(this) + 1}`;
4092
+ }
4093
+ /**
4094
+ * @internal Relationship ids for the parts this sheet generates itself.
4095
+ *
4096
+ * Derived purely from the model so the worksheet XML and the sheet's rels part
4097
+ * — which are written by different code — cannot disagree about an id. Order is
4098
+ * fixed: drawing, then external hyperlinks in insertion order, then the
4099
+ * comments part and its legacy VML drawing.
4100
+ */
4101
+ generatedRels() {
4102
+ let next = __privateMethod(this, _Worksheet_instances, relIdBase_fn).call(this);
4103
+ const result = { hyperlinks: /* @__PURE__ */ new Map() };
4104
+ if (this.hasDrawings) result.drawing = `rId${++next}`;
4105
+ for (const link of __privateGet(this, _hyperlinks).values()) {
4106
+ if (link.target) result.hyperlinks.set(link.ref, `rId${++next}`);
2576
4107
  }
2577
- return `rId${max + 1}`;
4108
+ if (this.ownsCommentParts) {
4109
+ result.comments = `rId${++next}`;
4110
+ result.vmlDrawing = `rId${++next}`;
4111
+ }
4112
+ return result;
2578
4113
  }
2579
4114
  /** @internal Captures round-trip state from the source package. */
2580
4115
  loadPreservedState(worksheetXml, relationships) {
2581
- __privateSet(this, _preservedRels, [...relationships]);
2582
- const known = new Set(relationships.map((r) => r.id));
4116
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
4117
+ __privateSet(this, _preservedRels, relationships.filter((r) => r.type !== REL_TYPES.hyperlink));
4118
+ const known = new Set(__privateGet(this, _preservedRels).map((r) => r.id));
2583
4119
  __privateSet(this, _preservedTail, parseTailElements(worksheetXml).filter((t) => known.has(t.relId)));
4120
+ const byId = new Map(relationships.map((r) => [r.id, r]));
4121
+ for (const [ref, relId] of __privateGet(this, _pendingHyperlinkRels)) {
4122
+ const rel = byId.get(relId);
4123
+ const link = __privateGet(this, _hyperlinks).get(ref);
4124
+ if (!rel || !link) continue;
4125
+ __privateGet(this, _hyperlinks).set(ref, { ...link, target: rel.target });
4126
+ }
4127
+ __privateGet(this, _pendingHyperlinkRels).clear();
4128
+ for (const [ref, link] of [...__privateGet(this, _hyperlinks)]) {
4129
+ if (!link.target && !link.location) __privateGet(this, _hyperlinks).delete(ref);
4130
+ }
4131
+ }
4132
+ /**
4133
+ * @internal Package paths of the comment parts this sheet read at load time.
4134
+ * The save pipeline drops them from the preserved set once the sheet takes
4135
+ * ownership, so they are not written twice.
4136
+ */
4137
+ commentPartPaths() {
4138
+ const paths = [];
4139
+ for (const rel of __privateGet(this, _preservedRels)) {
4140
+ if (!rel.resolved) continue;
4141
+ if (rel.type === REL_TYPES.comments || rel.type === REL_TYPES.vmlDrawing) {
4142
+ paths.push(rel.resolved);
4143
+ }
4144
+ }
4145
+ return paths;
4146
+ }
4147
+ /**
4148
+ * @internal The relationship pointing at this sheet's comments part, if the
4149
+ * sheet was loaded from a file that had one.
4150
+ */
4151
+ commentsPartPath() {
4152
+ return __privateGet(this, _preservedRels).find((r) => r.type === REL_TYPES.comments)?.resolved;
2584
4153
  }
2585
4154
  // ── XML serialisation ──────────────────────────────────────────────────────
2586
4155
  /** Builds the xl/worksheets/sheetN.xml string. */
2587
4156
  buildXml() {
4157
+ const memo = __privateGet(this, _xmlMemo);
4158
+ if (memo && memo.at === __privateGet(this, _version2).v) return memo.xml;
4159
+ const rels = this.generatedRels();
4160
+ const sheetViewXml = __privateMethod(this, _Worksheet_instances, buildSheetViewXml_fn).call(this);
2588
4161
  const colsXml = __privateMethod(this, _Worksheet_instances, buildColsXml_fn).call(this);
2589
4162
  const sheetDataXml = __privateMethod(this, _Worksheet_instances, buildSheetDataXml_fn).call(this);
2590
- const mergeXml = __privateMethod(this, _Worksheet_instances, buildMergeCellsXml_fn).call(this);
4163
+ const protectionXml = __privateMethod(this, _Worksheet_instances, buildSheetProtectionXml_fn).call(this);
2591
4164
  const filterXml = __privateGet(this, _autoFilter) ? `<autoFilter ref="${__privateGet(this, _autoFilter).ref}"/>` : "";
4165
+ const mergeXml = __privateMethod(this, _Worksheet_instances, buildMergeCellsXml_fn).call(this);
2592
4166
  const validXml = __privateMethod(this, _Worksheet_instances, buildDataValidationsXml_fn).call(this);
2593
- const sheetViewXml = __privateMethod(this, _Worksheet_instances, buildSheetViewXml_fn).call(this);
2594
- const preservedTailXml = __privateMethod(this, _Worksheet_instances, activeTail_fn).call(this).map((t) => t.xml).join("");
2595
- const drawingXml = this.hasDrawings ? `<drawing r:id="${this.drawingRelId}"/>` : "";
2596
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
4167
+ const linksXml = __privateMethod(this, _Worksheet_instances, buildHyperlinksXml_fn).call(this, rels.hyperlinks);
4168
+ const tailXml = __privateMethod(this, _Worksheet_instances, buildTailXml_fn).call(this, rels);
4169
+ const xml2 = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2597
4170
  <worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
2598
4171
  xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
2599
4172
  <sheetViews>${sheetViewXml}</sheetViews>
2600
- ${colsXml}${sheetDataXml}${filterXml}${mergeXml}${validXml}${preservedTailXml}${drawingXml}
4173
+ ${colsXml}${sheetDataXml}${protectionXml}${filterXml}${mergeXml}${validXml}${linksXml}${tailXml}
2601
4174
  </worksheet>`;
4175
+ __privateSet(this, _xmlMemo, { at: __privateGet(this, _version2).v, xml: xml2 });
4176
+ return xml2;
2602
4177
  }
2603
4178
  /** Parses xl/worksheets/sheetN.xml and loads cells into memory. */
2604
4179
  loadFromXml(xml2) {
4180
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
2605
4181
  __privateGet(this, _rows).clear();
2606
4182
  __privateSet(this, _cols, []);
2607
4183
  __privateSet(this, _mergeCells, []);
2608
4184
  __privateSet(this, _pane, void 0);
2609
4185
  __privateSet(this, _autoFilter, void 0);
2610
4186
  __privateSet(this, _dataValidations, []);
4187
+ __privateSet(this, _protection, void 0);
4188
+ __privateSet(this, _protectionCredentials, "");
4189
+ __privateGet(this, _hyperlinks).clear();
4190
+ __privateGet(this, _pendingHyperlinkRels).clear();
2611
4191
  const root = parseXml(xml2);
2612
4192
  for (const colEl of getElementsByTagName(root, "col")) {
4193
+ const widthAttr = getAttr(colEl, "width");
2613
4194
  __privateGet(this, _cols).push({
2614
4195
  min: parseInt(getAttr(colEl, "min") ?? "1", 10),
2615
4196
  max: parseInt(getAttr(colEl, "max") ?? "1", 10),
2616
- width: parseFloat(getAttr(colEl, "width") ?? "8"),
4197
+ width: widthAttr !== void 0 ? parseFloat(widthAttr) : void 0,
2617
4198
  customWidth: getAttr(colEl, "customWidth") === "1",
2618
4199
  hidden: getAttr(colEl, "hidden") === "1"
2619
4200
  });
2620
4201
  }
4202
+ const sharedMasters = /* @__PURE__ */ new Map();
4203
+ const sharedInstances = [];
2621
4204
  for (const rowEl of getElementsByTagName(root, "row")) {
2622
4205
  const rowIndex = parseInt(getAttr(rowEl, "r") ?? "0", 10);
2623
4206
  if (rowIndex < 1) continue;
@@ -2638,9 +4221,17 @@ ${colsXml}${sheetDataXml}${filterXml}${mergeXml}${validXml}${preservedTailXml}${
2638
4221
  ref.toUpperCase()
2639
4222
  );
2640
4223
  rowData.cells.set(ref.toUpperCase(), cellData);
4224
+ if (cellData.sharedIndex !== void 0) {
4225
+ if (cellData.formula !== void 0) {
4226
+ sharedMasters.set(cellData.sharedIndex, { formula: cellData.formula, ref: cellData.reference });
4227
+ } else {
4228
+ sharedInstances.push(cellData);
4229
+ }
4230
+ }
2641
4231
  }
2642
4232
  __privateGet(this, _rows).set(rowIndex, rowData);
2643
4233
  }
4234
+ __privateMethod(this, _Worksheet_instances, resolveSharedFormulas_fn).call(this, sharedMasters, sharedInstances);
2644
4235
  for (const mcEl of getElementsByTagName(root, "mergeCell")) {
2645
4236
  const ref = getAttr(mcEl, "ref");
2646
4237
  if (ref) __privateGet(this, _mergeCells).push({ ref });
@@ -2665,6 +4256,76 @@ ${colsXml}${sheetDataXml}${filterXml}${mergeXml}${validXml}${preservedTailXml}${
2665
4256
  const formula = f1Els[0]?.textContent ?? "";
2666
4257
  if (sqref && formula) __privateGet(this, _dataValidations).push({ sqref, formula });
2667
4258
  }
4259
+ const protEl = getElementsByTagName(root, "sheetProtection")[0];
4260
+ if (protEl && getAttr(protEl, "sheet") === "1") {
4261
+ const protection = { ...PROTECTION_DEFAULTS };
4262
+ for (const [key, attr] of PROTECTION_ATTRS) {
4263
+ const raw = getAttr(protEl, attr);
4264
+ if (raw !== void 0) protection[key] = raw !== "1";
4265
+ }
4266
+ __privateSet(this, _protection, protection);
4267
+ let credentials = "";
4268
+ for (const attr of ["password", "algorithmName", "hashValue", "saltValue", "spinCount"]) {
4269
+ const raw = getAttr(protEl, attr);
4270
+ if (raw !== void 0) credentials += ` ${attr}="${escXmlAttr(raw)}"`;
4271
+ }
4272
+ __privateSet(this, _protectionCredentials, credentials);
4273
+ }
4274
+ for (const hlEl of getElementsByTagName(root, "hyperlink")) {
4275
+ const ref = getAttr(hlEl, "ref");
4276
+ if (!ref) continue;
4277
+ const normalized = normalizeRangeRef(ref);
4278
+ __privateGet(this, _hyperlinks).set(normalized, {
4279
+ ref: normalized,
4280
+ location: getAttr(hlEl, "location"),
4281
+ display: getAttr(hlEl, "display"),
4282
+ tooltip: getAttr(hlEl, "tooltip")
4283
+ });
4284
+ const relId = getAttr(hlEl, "r:id") ?? getAttr(hlEl, "id");
4285
+ if (relId) __privateGet(this, _pendingHyperlinkRels).set(normalized, relId);
4286
+ }
4287
+ }
4288
+ /** @internal Parses an `xl/comments*.xml` part into this sheet's comments. */
4289
+ loadCommentsXml(xml2) {
4290
+ __privateMethod(this, _Worksheet_instances, touch_fn2).call(this);
4291
+ const root = parseXml(xml2);
4292
+ const authors = getElementsByTagName(root, "author").map((a) => a.textContent);
4293
+ for (const cEl of getElementsByTagName(root, "comment")) {
4294
+ const ref = getAttr(cEl, "ref");
4295
+ if (!ref) continue;
4296
+ const authorId = parseInt(getAttr(cEl, "authorId") ?? "0", 10);
4297
+ const text = getElementsByTagName(cEl, "t").map((t) => t.textContent).join("");
4298
+ const normalized = normalizeRef(ref);
4299
+ __privateGet(this, _comments).set(normalized, {
4300
+ ref: normalized,
4301
+ text,
4302
+ author: authors[authorId] ?? ""
4303
+ });
4304
+ }
4305
+ __privateSet(this, _commentsDirty, false);
4306
+ }
4307
+ /** @internal The comments part content, when this sheet owns it. */
4308
+ buildCommentsXml() {
4309
+ const authors = [...new Set([...__privateGet(this, _comments).values()].map((c) => c.author))];
4310
+ const authorsXml = authors.map((a) => `<author>${escXml2(a)}</author>`).join("");
4311
+ const listXml = [...__privateGet(this, _comments).values()].map(
4312
+ (c) => `<comment ref="${c.ref}" authorId="${authors.indexOf(c.author)}"><text><r><t xml:space="preserve">${escXml2(c.text)}</t></r></text></comment>`
4313
+ ).join("");
4314
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
4315
+ <comments xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><authors>${authorsXml}</authors><commentList>${listXml}</commentList></comments>`;
4316
+ }
4317
+ /**
4318
+ * @internal The legacy VML drawing that positions the comment boxes. Excel
4319
+ * needs it to draw the note indicator and popup; the comments part alone
4320
+ * carries only the text.
4321
+ */
4322
+ buildCommentsVml() {
4323
+ const shapes = [...__privateGet(this, _comments).values()].map((c, idx) => {
4324
+ const { row, column } = parseCellRef(c.ref);
4325
+ const anchor = `${column}, 15, ${row - 1}, 10, ${column + 2}, 15, ${row + 3}, 4`;
4326
+ return `<v:shape id="_x0000_s${1025 + idx}" type="#_x0000_t202" style="position:absolute;margin-left:60pt;margin-top:5pt;width:108pt;height:60pt;z-index:${idx + 1};visibility:hidden" fillcolor="#ffffe1" o:insetmode="auto"><v:fill color2="#ffffe1"/><v:shadow on="t" color="black" obscured="t"/><v:path o:connecttype="none"/><v:textbox style="mso-direction-alt:auto"><div style="text-align:left"/></v:textbox><x:ClientData ObjectType="Note"><x:MoveWithCells/><x:SizeWithCells/><x:Anchor>${anchor}</x:Anchor><x:AutoFill>False</x:AutoFill><x:Row>${row - 1}</x:Row><x:Column>${column - 1}</x:Column></x:ClientData></v:shape>`;
4327
+ }).join("");
4328
+ return `<xml xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel"><o:shapelayout v:ext="edit"><o:idmap v:ext="edit" data="1"/></o:shapelayout><v:shapetype id="_x0000_t202" coordsize="21600,21600" o:spt="202" path="m,l,21600r21600,l21600,xe"><v:stroke joinstyle="miter"/><v:path gradientshapeok="t" o:connecttype="rect"/></v:shapetype>${shapes}</xml>`;
2668
4329
  }
2669
4330
  };
2670
4331
  _sharedStrings2 = new WeakMap();
@@ -2678,23 +4339,331 @@ _autoFilter = new WeakMap();
2678
4339
  _dataValidations = new WeakMap();
2679
4340
  _visibility = new WeakMap();
2680
4341
  _drawings = new WeakMap();
4342
+ _protection = new WeakMap();
4343
+ _protectionCredentials = new WeakMap();
4344
+ _hyperlinks = new WeakMap();
4345
+ _comments = new WeakMap();
4346
+ _commentsDirty = new WeakMap();
4347
+ _pendingHyperlinkRels = new WeakMap();
2681
4348
  _preservedTail = new WeakMap();
2682
4349
  _preservedRels = new WeakMap();
4350
+ _version2 = new WeakMap();
4351
+ _usedBoundsMemo = new WeakMap();
4352
+ _xmlMemo = new WeakMap();
2683
4353
  _Worksheet_instances = new WeakSet();
4354
+ /** Records a change to this sheet's contents or layout. */
4355
+ touch_fn2 = function() {
4356
+ __privateGet(this, _version2).v++;
4357
+ };
4358
+ /**
4359
+ * The stored record for a cell, without creating it — the read paths below
4360
+ * work on this directly instead of wrapping it in a {@link Cell}. Formula
4361
+ * evaluation reads far more cells than it writes, and one wrapper per read is
4362
+ * pure garbage.
4363
+ */
4364
+ findCellData_fn = function(reference) {
4365
+ const ref = normalizeRefFast(reference);
4366
+ return __privateGet(this, _rows).get(parseCellRef(ref).row)?.cells.get(ref);
4367
+ };
4368
+ /**
4369
+ * The extent, computed without touching the memo.
4370
+ *
4371
+ * Internal callers must use this. A mutator bumps the version once, at its
4372
+ * start, and then does its work; if it read the memoised getter part-way
4373
+ * through, the half-finished extent would be cached against the *new* version
4374
+ * and served as current for the rest of that version's life. That is precisely
4375
+ * how `insertColumns` came to report the extent it had before the insert —
4376
+ * caught by the round-trip fuzz test, not by any of the hand-written ones.
4377
+ */
4378
+ computeUsedBounds_fn = function() {
4379
+ let rowCount = 0;
4380
+ let columnCount = 0;
4381
+ for (const row of __privateGet(this, _rows).values()) {
4382
+ if (row.cells.size === 0) continue;
4383
+ if (row.rowIndex > rowCount) rowCount = row.rowIndex;
4384
+ for (const ref of row.cells.keys()) {
4385
+ const col = columnFromRef(ref, refSplit(ref));
4386
+ if (col > columnCount) columnCount = col;
4387
+ }
4388
+ }
4389
+ return { rowCount, columnCount };
4390
+ };
4391
+ /** Removes and returns a cell's data record, pruning an emptied bare row. */
4392
+ takeCellData_fn = function(ref) {
4393
+ return __privateMethod(this, _Worksheet_instances, takeCellDataAt_fn).call(this, parseCellRef(ref).row, ref);
4394
+ };
4395
+ takeCellDataAt_fn = function(row, ref) {
4396
+ const rowData = __privateGet(this, _rows).get(row);
4397
+ const data = rowData?.cells.get(ref);
4398
+ if (rowData && data) {
4399
+ rowData.cells.delete(ref);
4400
+ if (rowData.cells.size === 0 && !rowData.hidden && rowData.height === void 0) {
4401
+ __privateGet(this, _rows).delete(row);
4402
+ }
4403
+ }
4404
+ return data;
4405
+ };
4406
+ deleteCellData_fn = function(ref) {
4407
+ __privateMethod(this, _Worksheet_instances, takeCellData_fn).call(this, ref);
4408
+ };
4409
+ applyStructural_fn = function(dim, kind, at, count) {
4410
+ const what = dim === "row" ? "Row" : "Column";
4411
+ if (!Number.isInteger(at) || at < 1) throw new RangeError(`${what} index must be an integer >= 1.`);
4412
+ if (!Number.isInteger(count) || count < 1) throw new RangeError("Count must be an integer >= 1.");
4413
+ const shift = { dim, kind, at, count, sheetName: __privateGet(this, _name) };
4414
+ if (kind === "insert") {
4415
+ const limit = dim === "row" ? EXCEL_MAX_ROWS : EXCEL_MAX_COLUMNS;
4416
+ const used = dim === "row" ? __privateMethod(this, _Worksheet_instances, maxPopulatedRow_fn).call(this) : __privateMethod(this, _Worksheet_instances, computeUsedBounds_fn).call(this).columnCount;
4417
+ if (used >= at && used + count > limit) {
4418
+ throw new RangeError(`Insert would push populated cells past the sheet limit of ${limit}.`);
4419
+ }
4420
+ }
4421
+ __privateMethod(this, _Worksheet_instances, shiftRowsAndCells_fn).call(this, shift);
4422
+ const refErrors = __privateMethod(this, _Worksheet_instances, rewriteFormulas_fn).call(this, shift);
4423
+ __privateMethod(this, _Worksheet_instances, shiftMerges_fn).call(this, shift);
4424
+ __privateMethod(this, _Worksheet_instances, shiftAutoFilter_fn).call(this, shift);
4425
+ __privateMethod(this, _Worksheet_instances, shiftValidations_fn).call(this, shift);
4426
+ __privateMethod(this, _Worksheet_instances, shiftHyperlinks_fn).call(this, shift);
4427
+ __privateMethod(this, _Worksheet_instances, shiftComments_fn).call(this, shift);
4428
+ if (dim === "col") __privateMethod(this, _Worksheet_instances, shiftColDefs_fn).call(this, shift);
4429
+ return { refErrors };
4430
+ };
4431
+ /**
4432
+ * Re-keys the row map / per-row cell maps and shifts every cell's spill range.
4433
+ *
4434
+ * Rows and columns before the shift boundary keep their keys, their reference
4435
+ * strings and their identity, so an edit near the end of a sheet costs
4436
+ * proportionally little rather than rebuilding the whole model. Spill ranges
4437
+ * are the one thing that must be visited everywhere, since a range belonging
4438
+ * to an unmoved anchor can still span the boundary — but that pass is a bare
4439
+ * property test per cell.
4440
+ */
4441
+ shiftRowsAndCells_fn = function(shift) {
4442
+ if (shift.dim === "row") __privateMethod(this, _Worksheet_instances, shiftRows_fn).call(this, shift);
4443
+ else __privateMethod(this, _Worksheet_instances, shiftColumns_fn).call(this, shift);
4444
+ };
4445
+ shiftRows_fn = function(shift) {
4446
+ const moved = [];
4447
+ for (const row of __privateGet(this, _rows).values()) {
4448
+ if (row.rowIndex < shift.at) {
4449
+ __privateMethod(this, _Worksheet_instances, shiftSpillRanges_fn).call(this, row, shift);
4450
+ continue;
4451
+ }
4452
+ const shifted = shiftIndex(row.rowIndex, shift);
4453
+ __privateGet(this, _rows).delete(row.rowIndex);
4454
+ if (shifted === null) continue;
4455
+ row.rowIndex = shifted;
4456
+ moved.push(row);
4457
+ }
4458
+ for (const row of moved) {
4459
+ const suffix = String(row.rowIndex);
4460
+ const newCells = /* @__PURE__ */ new Map();
4461
+ for (const cd of row.cells.values()) {
4462
+ const ref = cd.reference.slice(0, refSplit(cd.reference)) + suffix;
4463
+ cd.reference = ref;
4464
+ if (cd.arrayRef) cd.arrayRef = shiftRangeRef(cd.arrayRef, shift) ?? void 0;
4465
+ newCells.set(ref, cd);
4466
+ }
4467
+ row.cells = newCells;
4468
+ __privateGet(this, _rows).set(row.rowIndex, row);
4469
+ }
4470
+ };
4471
+ shiftColumns_fn = function(shift) {
4472
+ for (const row of __privateGet(this, _rows).values()) {
4473
+ let affected;
4474
+ for (const cd of row.cells.values()) {
4475
+ const ref = cd.reference;
4476
+ if (columnFromRef(ref, refSplit(ref)) < shift.at) {
4477
+ __privateMethod(this, _Worksheet_instances, shiftSpillRange_fn).call(this, cd, shift);
4478
+ continue;
4479
+ }
4480
+ (affected ?? (affected = [])).push(cd);
4481
+ }
4482
+ if (!affected) continue;
4483
+ for (const cd of affected) row.cells.delete(cd.reference);
4484
+ for (const cd of affected) {
4485
+ const split = refSplit(cd.reference);
4486
+ const shifted = shiftIndex(columnFromRef(cd.reference, split), shift);
4487
+ if (shifted === null) continue;
4488
+ const ref = columnLetter(shifted) + cd.reference.slice(split);
4489
+ cd.reference = ref;
4490
+ __privateMethod(this, _Worksheet_instances, shiftSpillRange_fn).call(this, cd, shift);
4491
+ row.cells.set(ref, cd);
4492
+ }
4493
+ }
4494
+ };
4495
+ /** Largest 1-based row index holding a cell, or 0. O(rows), no ref parsing. */
4496
+ maxPopulatedRow_fn = function() {
4497
+ let max = 0;
4498
+ for (const row of __privateGet(this, _rows).values()) {
4499
+ if (row.cells.size > 0 && row.rowIndex > max) max = row.rowIndex;
4500
+ }
4501
+ return max;
4502
+ };
4503
+ shiftSpillRanges_fn = function(row, shift) {
4504
+ for (const cd of row.cells.values()) __privateMethod(this, _Worksheet_instances, shiftSpillRange_fn).call(this, cd, shift);
4505
+ };
4506
+ shiftSpillRange_fn = function(cd, shift) {
4507
+ if (cd.arrayRef) cd.arrayRef = shiftRangeRef(cd.arrayRef, shift) ?? void 0;
4508
+ };
4509
+ /** Rewrites every formula on the sheet; returns refs that now hold #REF!. */
4510
+ rewriteFormulas_fn = function(shift) {
4511
+ const refErrors = [];
4512
+ for (const row of __privateGet(this, _rows).values()) {
4513
+ for (const cd of row.cells.values()) {
4514
+ if (!cd.formula) continue;
4515
+ const result = shiftFormulaRefs(cd.formula, shift, __privateGet(this, _name));
4516
+ if (result.changed) cd.formula = result.text;
4517
+ if (result.hasRefError) refErrors.push(cd.reference);
4518
+ }
4519
+ }
4520
+ return refErrors;
4521
+ };
4522
+ shiftMerges_fn = function(shift) {
4523
+ __privateSet(this, _mergeCells, __privateGet(this, _mergeCells).flatMap((m) => {
4524
+ const ref = shiftRangeRef(m.ref, shift);
4525
+ if (!ref) return [];
4526
+ const [a, b] = ref.split(":");
4527
+ if (!b || a === b) return [];
4528
+ return [{ ref }];
4529
+ }));
4530
+ };
4531
+ shiftAutoFilter_fn = function(shift) {
4532
+ if (!__privateGet(this, _autoFilter)) return;
4533
+ const ref = shiftRangeRef(__privateGet(this, _autoFilter).ref, shift);
4534
+ __privateSet(this, _autoFilter, ref ? { ref } : void 0);
4535
+ };
4536
+ shiftValidations_fn = function(shift) {
4537
+ __privateSet(this, _dataValidations, __privateGet(this, _dataValidations).flatMap((dv) => {
4538
+ const sqref = shiftSqref(dv.sqref, shift);
4539
+ if (!sqref) return [];
4540
+ const formula = shiftFormulaRefs(dv.formula, shift, __privateGet(this, _name)).text;
4541
+ return [{ sqref, formula }];
4542
+ }));
4543
+ };
4544
+ shiftHyperlinks_fn = function(shift) {
4545
+ const moved = /* @__PURE__ */ new Map();
4546
+ for (const link of __privateGet(this, _hyperlinks).values()) {
4547
+ const ref = shiftRangeRef(link.ref, shift);
4548
+ if (!ref) continue;
4549
+ moved.set(ref, { ...link, ref });
4550
+ }
4551
+ __privateSet(this, _hyperlinks, moved);
4552
+ };
4553
+ shiftComments_fn = function(shift) {
4554
+ const moved = /* @__PURE__ */ new Map();
4555
+ let changed = false;
4556
+ for (const comment of __privateGet(this, _comments).values()) {
4557
+ const ref = shiftRangeRef(comment.ref, shift);
4558
+ if (!ref) {
4559
+ changed = true;
4560
+ continue;
4561
+ }
4562
+ if (ref !== comment.ref) changed = true;
4563
+ moved.set(ref, { ...comment, ref });
4564
+ }
4565
+ __privateSet(this, _comments, moved);
4566
+ if (changed) __privateSet(this, _commentsDirty, true);
4567
+ };
4568
+ shiftColDefs_fn = function(shift) {
4569
+ __privateSet(this, _cols, __privateGet(this, _cols).flatMap((c) => {
4570
+ const span = shiftSpan(c.min, c.max, shift);
4571
+ return span ? [{ ...c, min: span.start, max: span.end }] : [];
4572
+ }));
4573
+ };
4574
+ /** Highest numeric preserved relationship id, so generated ids clear them all. */
4575
+ relIdBase_fn = function() {
4576
+ let max = 0;
4577
+ for (const r of __privateGet(this, _preservedRels)) {
4578
+ const n = RID_PATTERN.exec(r.id);
4579
+ if (n) max = Math.max(max, parseInt(n[1], 10));
4580
+ }
4581
+ return max;
4582
+ };
2684
4583
  /**
2685
4584
  * Preserved leaf elements that will actually be emitted. A sheet may only have
2686
4585
  * one `<drawing>`, so live providers suppress a preserved one — every consumer
2687
4586
  * (serialisation, relationships, pruning) must agree on that, hence one helper.
2688
4587
  */
2689
4588
  activeTail_fn = function() {
2690
- return __privateGet(this, _preservedTail).filter((t) => !(t.tag === "drawing" && this.hasDrawings));
4589
+ return __privateGet(this, _preservedTail).filter((t) => {
4590
+ if (t.tag === "drawing") return !this.hasDrawings;
4591
+ if (t.tag === "legacyDrawing") return !this.commentPartsReplaced;
4592
+ return true;
4593
+ });
4594
+ };
4595
+ /**
4596
+ * The relationship-bearing leaf elements, in schema order: `drawing`,
4597
+ * `legacyDrawing`, `legacyDrawingHF`, `picture`. A sheet may hold only one of
4598
+ * each, so generated content replaces the preserved element of the same tag —
4599
+ * live drawing providers replace a preserved `<drawing>`, and comments this
4600
+ * sheet now owns replace a preserved `<legacyDrawing>`.
4601
+ */
4602
+ buildTailXml_fn = function(rels) {
4603
+ const preserved = __privateMethod(this, _Worksheet_instances, activeTail_fn).call(this);
4604
+ const byTag = (tag) => preserved.filter((t) => t.tag === tag).map((t) => t.xml).join("");
4605
+ const drawing = rels.drawing ? `<drawing r:id="${rels.drawing}"/>` : byTag("drawing");
4606
+ const legacy = rels.vmlDrawing ? `<legacyDrawing r:id="${rels.vmlDrawing}"/>` : byTag("legacyDrawing");
4607
+ return drawing + legacy + byTag("legacyDrawingHF") + byTag("picture");
4608
+ };
4609
+ buildSheetProtectionXml_fn = function() {
4610
+ if (!__privateGet(this, _protection)) return "";
4611
+ let attrs = ' sheet="1"';
4612
+ for (const [key, attr] of PROTECTION_ATTRS) {
4613
+ const blocked = !__privateGet(this, _protection)[key];
4614
+ const defaultBlocked = !PROTECTION_DEFAULTS[key];
4615
+ if (blocked !== defaultBlocked) attrs += ` ${attr}="${blocked ? 1 : 0}"`;
4616
+ }
4617
+ return `<sheetProtection${__privateGet(this, _protectionCredentials)}${attrs}/>`;
4618
+ };
4619
+ buildHyperlinksXml_fn = function(relIds) {
4620
+ if (__privateGet(this, _hyperlinks).size === 0) return "";
4621
+ const inner = [...__privateGet(this, _hyperlinks).values()].map((link) => {
4622
+ const relId = relIds.get(link.ref);
4623
+ const parts = [`ref="${link.ref}"`];
4624
+ if (relId) parts.push(`r:id="${relId}"`);
4625
+ if (link.location) parts.push(`location="${escXmlAttr(link.location)}"`);
4626
+ if (link.display) parts.push(`display="${escXmlAttr(link.display)}"`);
4627
+ if (link.tooltip) parts.push(`tooltip="${escXmlAttr(link.tooltip)}"`);
4628
+ return `<hyperlink ${parts.join(" ")}/>`;
4629
+ }).join("");
4630
+ return `<hyperlinks>${inner}</hyperlinks>`;
4631
+ };
4632
+ /**
4633
+ * Materialises the formula of every shared-formula instance cell.
4634
+ *
4635
+ * Excel writes a fill-down once: the group's master carries the text, the rest
4636
+ * carry `<f t="shared" si="n"/>` and are understood to hold the master's
4637
+ * formula with its relative references moved by the offset between the cells.
4638
+ * Read literally — the way this used to be read — an instance cell has no
4639
+ * formula at all, and the next save turns it into the constant that happened to
4640
+ * be cached in `<v>`. On a real workbook that is tens of thousands of formulas
4641
+ * quietly replaced by numbers.
4642
+ *
4643
+ * Each cell ends up owning its own text, so nothing downstream (evaluation,
4644
+ * structural edits, serialisation) needs to know the group ever existed.
4645
+ */
4646
+ resolveSharedFormulas_fn = function(masters, instances) {
4647
+ if (instances.length === 0) return;
4648
+ for (const cd of instances) {
4649
+ const master = masters.get(cd.sharedIndex);
4650
+ if (!master) continue;
4651
+ const from = parseCellRef(master.ref);
4652
+ const to = parseCellRef(cd.reference);
4653
+ cd.formula = translateFormulaRefs(
4654
+ master.formula,
4655
+ to.row - from.row,
4656
+ to.column - from.column
4657
+ ).text;
4658
+ }
2691
4659
  };
2692
4660
  // ── Private XML builders ───────────────────────────────────────────────────
2693
4661
  buildColsXml_fn = function() {
2694
4662
  if (__privateGet(this, _cols).length === 0) return "";
2695
4663
  const inner = __privateGet(this, _cols).map((c) => {
4664
+ const width = c.width !== void 0 ? ` width="${c.width.toFixed(2)}" customWidth="${c.customWidth ? 1 : 0}"` : "";
2696
4665
  const hidden = c.hidden ? ' hidden="1"' : "";
2697
- return `<col min="${c.min}" max="${c.max}" width="${c.width.toFixed(2)}" customWidth="${c.customWidth ? 1 : 0}"${hidden}/>`;
4666
+ return `<col min="${c.min}" max="${c.max}"${width}${hidden}/>`;
2698
4667
  }).join("");
2699
4668
  return `<cols>${inner}</cols>`;
2700
4669
  };
@@ -2704,12 +4673,8 @@ buildSheetDataXml_fn = function() {
2704
4673
  const rowsXml = sortedRows.map((row) => {
2705
4674
  const ht = row.height !== void 0 && row.customHeight ? ` ht="${row.height}" customHeight="1"` : "";
2706
4675
  const hidden = row.hidden ? ' hidden="1"' : "";
2707
- const sortedCells = [...row.cells.values()].sort((a, b) => {
2708
- const ra = parseCellRef(a.reference);
2709
- const rb = parseCellRef(b.reference);
2710
- return ra.column - rb.column;
2711
- });
2712
- const cellsXml = sortedCells.map((cd) => new Cell(cd, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2)).toXml()).filter(Boolean).join("");
4676
+ const sortedCells = [...row.cells.values()].map((cd) => ({ cd, col: columnFromRef(cd.reference, refSplit(cd.reference)) })).sort((a, b) => a.col - b.col);
4677
+ const cellsXml = sortedCells.map(({ cd }) => buildCellXml(cd)).filter(Boolean).join("");
2713
4678
  if (!cellsXml && !ht && !hidden) return "";
2714
4679
  return `<row r="${row.rowIndex}"${ht}${hidden}>${cellsXml}</row>`;
2715
4680
  }).filter(Boolean).join("");
@@ -2738,6 +4703,61 @@ buildSheetViewXml_fn = function() {
2738
4703
  const ySplit = row > 0 ? ` ySplit="${row}"` : "";
2739
4704
  return `<sheetView workbookViewId="0"><pane${xSplit}${ySplit} topLeftCell="${topLeft}" activePane="${activePane}" state="frozen"/></sheetView>`;
2740
4705
  };
4706
+ /**
4707
+ * Returns the single-column ColDef covering `columnIndex`, splitting a wider
4708
+ * span into up to three pieces so the other columns keep their width/hidden
4709
+ * state. Returns undefined when no definition covers the column.
4710
+ */
4711
+ splitColSpan_fn = function(columnIndex) {
4712
+ const idx = __privateMethod(this, _Worksheet_instances, findColDefIndex_fn).call(this, columnIndex);
4713
+ if (idx === -1) return void 0;
4714
+ const span = __privateGet(this, _cols)[idx];
4715
+ if (span.min === span.max) return span;
4716
+ const pieces = [];
4717
+ if (span.min < columnIndex) pieces.push({ ...span, max: columnIndex - 1 });
4718
+ const target = { ...span, min: columnIndex, max: columnIndex };
4719
+ pieces.push(target);
4720
+ if (span.max > columnIndex) pieces.push({ ...span, min: columnIndex + 1 });
4721
+ __privateGet(this, _cols).splice(idx, 1, ...pieces);
4722
+ return target;
4723
+ };
4724
+ /**
4725
+ * Inserts a definition keeping `#cols` sorted by `min`. Binary insertion,
4726
+ * because re-sorting the whole array per call makes setting widths for every
4727
+ * column of a wide sheet quadratic.
4728
+ */
4729
+ insertColDef_fn = function(def) {
4730
+ let lo = 0;
4731
+ let hi = __privateGet(this, _cols).length;
4732
+ while (lo < hi) {
4733
+ const mid = lo + hi >>> 1;
4734
+ if (__privateGet(this, _cols)[mid].min < def.min) lo = mid + 1;
4735
+ else hi = mid;
4736
+ }
4737
+ __privateGet(this, _cols).splice(lo, 0, def);
4738
+ return def;
4739
+ };
4740
+ /** Index of the definition covering `columnIndex`, or -1. Binary search. */
4741
+ findColDefIndex_fn = function(columnIndex) {
4742
+ let lo = 0;
4743
+ let hi = __privateGet(this, _cols).length - 1;
4744
+ while (lo <= hi) {
4745
+ const mid = lo + hi >>> 1;
4746
+ const c = __privateGet(this, _cols)[mid];
4747
+ if (columnIndex < c.min) hi = mid - 1;
4748
+ else if (columnIndex > c.max) lo = mid + 1;
4749
+ else return mid;
4750
+ }
4751
+ return -1;
4752
+ };
4753
+ findColDef_fn = function(columnIndex) {
4754
+ const idx = __privateMethod(this, _Worksheet_instances, findColDefIndex_fn).call(this, columnIndex);
4755
+ return idx === -1 ? void 0 : __privateGet(this, _cols)[idx];
4756
+ };
4757
+ /** Drops definitions that no longer carry any information. */
4758
+ pruneColDefs_fn = function() {
4759
+ __privateSet(this, _cols, __privateGet(this, _cols).filter((c) => c.width !== void 0 || c.customWidth || c.hidden));
4760
+ };
2741
4761
  getOrCreateRow_fn = function(rowIndex) {
2742
4762
  let row = __privateGet(this, _rows).get(rowIndex);
2743
4763
  if (!row) {
@@ -2746,9 +4766,64 @@ getOrCreateRow_fn = function(rowIndex) {
2746
4766
  }
2747
4767
  return row;
2748
4768
  };
4769
+ var RID_PATTERN = /^rId(\d+)$/;
2749
4770
  function escXml2(s) {
2750
4771
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2751
4772
  }
4773
+ function escXmlAttr(s) {
4774
+ return escXml2(s).replace(/"/g, "&quot;");
4775
+ }
4776
+ function normalizeRef(ref) {
4777
+ const normalized = normalizeRefFast(ref);
4778
+ parseCellRef(normalized);
4779
+ return normalized;
4780
+ }
4781
+ function normalizeRangeRef(ref) {
4782
+ const normalized = normalizeRefFast(ref);
4783
+ const colon = normalized.indexOf(":");
4784
+ if (colon === -1) {
4785
+ parseCellRef(normalized);
4786
+ return normalized;
4787
+ }
4788
+ parseCellRef(normalized.slice(0, colon));
4789
+ parseCellRef(normalized.slice(colon + 1));
4790
+ return normalized;
4791
+ }
4792
+ function normalizeRefFast(ref) {
4793
+ return ref.indexOf("$") === -1 ? upperFast(ref) : ref.replace(/\$/g, "").toUpperCase();
4794
+ }
4795
+ function upperFast(s) {
4796
+ for (let i = 0; i < s.length; i++) {
4797
+ const code = s.charCodeAt(i);
4798
+ if (code >= 97 && code <= 122) return s.toUpperCase();
4799
+ }
4800
+ return s;
4801
+ }
4802
+ function refSplit(ref) {
4803
+ let i = 0;
4804
+ while (i < ref.length) {
4805
+ const code = ref.charCodeAt(i);
4806
+ if (code < 65 || code > 90) break;
4807
+ i++;
4808
+ }
4809
+ return i;
4810
+ }
4811
+ function columnFromRef(ref, split) {
4812
+ let col = 0;
4813
+ for (let i = 0; i < split; i++) col = col * 26 + (ref.charCodeAt(i) - 64);
4814
+ return col;
4815
+ }
4816
+ function translateRangeRef(ref, deltaRow, deltaCol) {
4817
+ const parts = ref.split(":").map((p) => {
4818
+ const { row, column } = parseCellRef(p);
4819
+ const r = row + deltaRow;
4820
+ const c = column + deltaCol;
4821
+ if (r < 1 || c < 1 || r > EXCEL_MAX_ROWS || c > EXCEL_MAX_COLUMNS) return null;
4822
+ return cellRefFromRowCol(r, c);
4823
+ });
4824
+ if (parts.some((p) => p === null)) return void 0;
4825
+ return parts.join(":");
4826
+ }
2752
4827
 
2753
4828
  // src/model/defined-name.ts
2754
4829
  var _names, _DefinedNameManager_instances, indexOf_fn;
@@ -2813,7 +4888,7 @@ indexOf_fn = function(name, scope) {
2813
4888
  };
2814
4889
 
2815
4890
  // src/model/workbook.ts
2816
- var _sharedStrings3, _styles3, _sheets, _definedNames, _closed, _preservedParts, _preservedContentTypes, _orphanedTargets, _Workbook_instances, assertOpen_fn, seedPreservedParts_fn, preservedContentTypeDeclarations_fn, buildZipEntries_fn;
4891
+ var _sharedStrings3, _styles3, _sheets, _definedNames, _closed, _calcChain, _calcChainFingerprint, _deflateCache, _preservedParts, _preservedContentTypes, _orphanedTargets, _Workbook_instances, structuralEdit_fn, sheetFingerprint_fn, calcChainStillValid_fn, assertOpen_fn, seedPreservedParts_fn, preservedContentTypeDeclarations_fn, buildZipEntries_fn;
2817
4892
  var _Workbook = class _Workbook {
2818
4893
  constructor(sharedStrings, styles) {
2819
4894
  __privateAdd(this, _Workbook_instances);
@@ -2822,6 +4897,24 @@ var _Workbook = class _Workbook {
2822
4897
  __privateAdd(this, _sheets, []);
2823
4898
  __privateAdd(this, _definedNames, new DefinedNameManager());
2824
4899
  __privateAdd(this, _closed, false);
4900
+ /**
4901
+ * The calculation order Excel stored in `xl/calcChain.xml`, in file order, or
4902
+ * undefined when the source had no such part. Read-only to consumers: it is a
4903
+ * record of what the source file said, not a live view of the model.
4904
+ */
4905
+ __privateAdd(this, _calcChain);
4906
+ /**
4907
+ * Fingerprint of the sheet set taken right after the calc chain was parsed.
4908
+ * The chain is only written back out when this still matches — see
4909
+ * {@link #calcChainStillValid}.
4910
+ */
4911
+ __privateAdd(this, _calcChainFingerprint);
4912
+ /**
4913
+ * Compressed form of each package part from the last save. Compression is where
4914
+ * a save spends its time, and an autosave changes one part, so the rest are
4915
+ * reused verbatim. Keyed on content, so a part rebuilt identically still hits.
4916
+ */
4917
+ __privateAdd(this, _deflateCache, createDeflateCache());
2825
4918
  /** Parts carried over verbatim from the file this workbook was opened from. */
2826
4919
  __privateAdd(this, _preservedParts, /* @__PURE__ */ new Map());
2827
4920
  __privateAdd(this, _preservedContentTypes);
@@ -2846,6 +4939,7 @@ var _Workbook = class _Workbook {
2846
4939
  * Mirrors Workbook.Open(stream) in C#.
2847
4940
  */
2848
4941
  static fromBuffer(buffer) {
4942
+ var _a;
2849
4943
  const entries = unzipXlsx(buffer);
2850
4944
  const ss = new SharedStringManager();
2851
4945
  const sm = new StyleManager();
@@ -2873,10 +4967,20 @@ var _Workbook = class _Workbook {
2873
4967
  const wsRelsXml = readXmlEntry(entries, `${dir}/_rels/${file}.rels`);
2874
4968
  if (wsRelsXml) {
2875
4969
  ws.loadPreservedState(wsXml, parseRelationships(wsRelsXml, wsPath));
4970
+ const commentsPath = ws.commentsPartPath();
4971
+ if (commentsPath) {
4972
+ const commentsXml = readXmlEntry(entries, commentsPath);
4973
+ if (commentsXml) ws.loadCommentsXml(commentsXml);
4974
+ }
2876
4975
  }
2877
4976
  }
2878
4977
  __privateGet(wb, _sheets).push({ worksheet: ws, sheetId, relId });
2879
4978
  }
4979
+ const calcChainXml = readXmlEntry(entries, CALC_CHAIN_PATH);
4980
+ if (calcChainXml) {
4981
+ __privateSet(wb, _calcChain, parseCalcChain(calcChainXml, __privateGet(wb, _sheets)));
4982
+ __privateSet(wb, _calcChainFingerprint, __privateMethod(_a = wb, _Workbook_instances, sheetFingerprint_fn).call(_a));
4983
+ }
2880
4984
  __privateSet(wb, _preservedParts, collectPreservedParts(entries));
2881
4985
  const ctXml = readXmlEntry(entries, "[Content_Types].xml");
2882
4986
  if (ctXml) __privateSet(wb, _preservedContentTypes, parseContentTypes(ctXml));
@@ -2923,8 +5027,8 @@ var _Workbook = class _Workbook {
2923
5027
  if (__privateGet(this, _sheets).some((e) => e.worksheet.name === name)) {
2924
5028
  throw new Error(`Worksheet "${name}" already exists.`);
2925
5029
  }
2926
- const sheetId = __privateGet(this, _sheets).length + 1;
2927
- const relId = `rId${sheetId}`;
5030
+ const sheetId = __privateGet(this, _sheets).reduce((max, e) => Math.max(max, e.sheetId), 0) + 1;
5031
+ const relId = `rId${__privateGet(this, _sheets).length + 1}`;
2928
5032
  const ws = new Worksheet(name, __privateGet(this, _sharedStrings3), __privateGet(this, _styles3));
2929
5033
  __privateGet(this, _sheets).push({ worksheet: ws, sheetId, relId });
2930
5034
  return ws;
@@ -2961,6 +5065,32 @@ var _Workbook = class _Workbook {
2961
5065
  if (!entry) throw new Error(`Worksheet "${name}" not found.`);
2962
5066
  return entry.worksheet;
2963
5067
  }
5068
+ // ── Structural editing (workbook-wide) ───────────────────────────────────────
5069
+ /**
5070
+ * Inserts rows on one sheet and keeps the whole workbook consistent:
5071
+ * formulas on OTHER sheets that reference the edited sheet, and defined
5072
+ * names, are rewritten too. Prefer this over `Worksheet.insertRows` in a
5073
+ * multi-sheet workbook.
5074
+ *
5075
+ * @returns The edited sheet's result; `refErrors` additionally includes
5076
+ * sheet-qualified refs (e.g. `"Other!B2"`) for affected formula
5077
+ * cells on other sheets.
5078
+ */
5079
+ insertRows(sheetName, at, count = 1) {
5080
+ return __privateMethod(this, _Workbook_instances, structuralEdit_fn).call(this, sheetName, "row", "insert", at, count);
5081
+ }
5082
+ /** Workbook-wide counterpart to `Worksheet.deleteRows`. See {@link insertRows}. */
5083
+ deleteRows(sheetName, at, count = 1) {
5084
+ return __privateMethod(this, _Workbook_instances, structuralEdit_fn).call(this, sheetName, "row", "delete", at, count);
5085
+ }
5086
+ /** Workbook-wide counterpart to `Worksheet.insertColumns`. See {@link insertRows}. */
5087
+ insertColumns(sheetName, at, count = 1) {
5088
+ return __privateMethod(this, _Workbook_instances, structuralEdit_fn).call(this, sheetName, "col", "insert", at, count);
5089
+ }
5090
+ /** Workbook-wide counterpart to `Worksheet.deleteColumns`. See {@link insertRows}. */
5091
+ deleteColumns(sheetName, at, count = 1) {
5092
+ return __privateMethod(this, _Workbook_instances, structuralEdit_fn).call(this, sheetName, "col", "delete", at, count);
5093
+ }
2964
5094
  // ── Defined names (named ranges) ─────────────────────────────────────────────
2965
5095
  /**
2966
5096
  * Defines (or replaces) a named range.
@@ -3007,7 +5137,21 @@ var _Workbook = class _Workbook {
3007
5137
  saveAsBuffer() {
3008
5138
  __privateMethod(this, _Workbook_instances, assertOpen_fn).call(this);
3009
5139
  const entries = __privateMethod(this, _Workbook_instances, buildZipEntries_fn).call(this);
3010
- return zipXlsx(entries);
5140
+ return zipXlsxCached(entries, __privateGet(this, _deflateCache));
5141
+ }
5142
+ /**
5143
+ * Same output as {@link saveAsBuffer}, without blocking the calling thread on
5144
+ * compression — for an editor that autosaves, this is the difference between a
5145
+ * visible stall on every save and none.
5146
+ *
5147
+ * The XML for sheets that have not changed since the last save is reused, so a
5148
+ * save after a one-cell edit re-serialises one sheet rather than all of them.
5149
+ * That reuse applies to the synchronous path too; only the compression differs.
5150
+ */
5151
+ async saveAsBufferAsync() {
5152
+ __privateMethod(this, _Workbook_instances, assertOpen_fn).call(this);
5153
+ const entries = __privateMethod(this, _Workbook_instances, buildZipEntries_fn).call(this);
5154
+ return zipXlsxCachedAsync(entries, __privateGet(this, _deflateCache));
3011
5155
  }
3012
5156
  /**
3013
5157
  * Saves the workbook to a file (Node.js only).
@@ -3015,7 +5159,7 @@ var _Workbook = class _Workbook {
3015
5159
  */
3016
5160
  async saveToFile(filePath) {
3017
5161
  __privateMethod(this, _Workbook_instances, assertOpen_fn).call(this);
3018
- const buffer = this.saveAsBuffer();
5162
+ const buffer = await this.saveAsBufferAsync();
3019
5163
  const fs = await import('fs/promises');
3020
5164
  const path = await import('path');
3021
5165
  const dir = path.dirname(filePath);
@@ -3031,16 +5175,72 @@ var _Workbook = class _Workbook {
3031
5175
  [Symbol.dispose]() {
3032
5176
  this.close();
3033
5177
  }
5178
+ // ── Calculation order ──────────────────────────────────────────────────────
5179
+ /**
5180
+ * The calculation order Excel recorded for this workbook, oldest-first as
5181
+ * stored in `xl/calcChain.xml`, or an empty array when the source file had
5182
+ * none (a freshly created workbook, or one saved by a tool that omits it).
5183
+ *
5184
+ * Excel has already done the topological sort that produced this order, so
5185
+ * evaluating in it avoids repeating that work. Treat it as a hint, not a
5186
+ * guarantee: it reflects the file as opened, and edits made since are not
5187
+ * reflected here.
5188
+ */
5189
+ get calcChain() {
5190
+ return __privateGet(this, _calcChain) ?? EMPTY_CALC_CHAIN;
5191
+ }
3034
5192
  };
3035
5193
  _sharedStrings3 = new WeakMap();
3036
5194
  _styles3 = new WeakMap();
3037
5195
  _sheets = new WeakMap();
3038
5196
  _definedNames = new WeakMap();
3039
5197
  _closed = new WeakMap();
5198
+ _calcChain = new WeakMap();
5199
+ _calcChainFingerprint = new WeakMap();
5200
+ _deflateCache = new WeakMap();
3040
5201
  _preservedParts = new WeakMap();
3041
5202
  _preservedContentTypes = new WeakMap();
3042
5203
  _orphanedTargets = new WeakMap();
3043
5204
  _Workbook_instances = new WeakSet();
5205
+ structuralEdit_fn = function(sheetName, dim, kind, at, count) {
5206
+ __privateMethod(this, _Workbook_instances, assertOpen_fn).call(this);
5207
+ const target = this.getWorksheet(sheetName);
5208
+ const result = dim === "row" ? kind === "insert" ? target.insertRows(at, count) : target.deleteRows(at, count) : kind === "insert" ? target.insertColumns(at, count) : target.deleteColumns(at, count);
5209
+ const shift = { dim, kind, at, count, sheetName: target.name };
5210
+ const refErrors = [...result.refErrors];
5211
+ for (const ws of this.worksheets) {
5212
+ if (ws === target) continue;
5213
+ const external = ws.applyExternalShift(shift);
5214
+ for (const ref of external.refErrors) refErrors.push(`${ws.name}!${ref}`);
5215
+ }
5216
+ for (const dn of [...__privateGet(this, _definedNames).all]) {
5217
+ const rewritten = shiftFormulaRefs(dn.refersTo, shift, dn.scope ?? "");
5218
+ if (rewritten.changed) __privateGet(this, _definedNames).define(dn.name, rewritten.text, dn.scope);
5219
+ }
5220
+ return { refErrors };
5221
+ };
5222
+ /**
5223
+ * Identity of the sheet set: names, durable ids and per-sheet mutation counts.
5224
+ * The calc chain is a statement about exactly this set — which cells hold
5225
+ * formulas, on which sheet, under which id — so any change to it invalidates
5226
+ * the chain wholesale.
5227
+ */
5228
+ sheetFingerprint_fn = function() {
5229
+ return __privateGet(this, _sheets).map((e) => `${e.sheetId}:${e.worksheet.name}:${e.worksheet.version}`).join("|");
5230
+ };
5231
+ /**
5232
+ * Whether the parsed calc chain may be written back out.
5233
+ *
5234
+ * Deliberately conservative: any edit at all (a value, a formula, a sheet
5235
+ * added or removed) and the chain is dropped instead of patched. An absent
5236
+ * calcChain is a documented no-op — Excel rebuilds it on open — whereas a
5237
+ * chain that disagrees with the sheets is the failure mode this whole change
5238
+ * exists to remove. Rebuilding a correct chain would mean redoing Excel's
5239
+ * dependency analysis, which belongs in the formula package, not here.
5240
+ */
5241
+ calcChainStillValid_fn = function() {
5242
+ return __privateGet(this, _calcChain) !== void 0 && __privateGet(this, _calcChain).length > 0 && __privateGet(this, _calcChainFingerprint) === __privateMethod(this, _Workbook_instances, sheetFingerprint_fn).call(this);
5243
+ };
3044
5244
  // ── Private helpers ────────────────────────────────────────────────────────
3045
5245
  assertOpen_fn = function() {
3046
5246
  if (__privateGet(this, _closed)) throw new Error("Workbook has been closed.");
@@ -3101,7 +5301,7 @@ buildZipEntries_fn = function() {
3101
5301
  entries.set("_rels/.rels", ROOT_RELS);
3102
5302
  const sheetMetas = __privateGet(this, _sheets).map((e, i) => ({
3103
5303
  name: e.worksheet.name,
3104
- sheetId: i + 1,
5304
+ sheetId: e.sheetId,
3105
5305
  relationshipId: `rId${i + 1}`,
3106
5306
  state: e.worksheet.visibility !== "visible" ? e.worksheet.visibility : void 0
3107
5307
  }));
@@ -3114,20 +5314,58 @@ buildZipEntries_fn = function() {
3114
5314
  return { name: dn.name, refersTo: dn.refersTo, localSheetId };
3115
5315
  });
3116
5316
  entries.set("xl/workbook.xml", buildWorkbookXml(sheetMetas, definedNameMetas));
3117
- entries.set("xl/_rels/workbook.xml.rels", buildWorkbookRels(sheetCount));
5317
+ const keepCalcChain = __privateMethod(this, _Workbook_instances, calcChainStillValid_fn).call(this);
5318
+ if (keepCalcChain) {
5319
+ const idBySheet = new Map(__privateGet(this, _sheets).map((e) => [e.worksheet.name, e.sheetId]));
5320
+ entries.set(CALC_CHAIN_PATH, buildCalcChainXml(__privateGet(this, _calcChain), idBySheet));
5321
+ } else {
5322
+ entries.delete(CALC_CHAIN_PATH);
5323
+ preservedPaths.delete(CALC_CHAIN_PATH);
5324
+ }
5325
+ entries.set("xl/_rels/workbook.xml.rels", buildWorkbookRels(sheetCount, keepCalcChain));
3118
5326
  entries.set("xl/styles.xml", __privateGet(this, _styles3).buildStylesXml());
3119
5327
  entries.set("xl/sharedStrings.xml", __privateGet(this, _sharedStrings3).buildXml());
3120
5328
  for (let i = 0; i < __privateGet(this, _sheets).length; i++) {
3121
5329
  entries.set(`xl/worksheets/sheet${i + 1}.xml`, __privateGet(this, _sheets)[i].worksheet.buildXml());
3122
5330
  }
3123
5331
  const extraOverrides = [];
5332
+ const extraDefaults = [];
5333
+ if (keepCalcChain) {
5334
+ extraOverrides.push({ partName: `/${CALC_CHAIN_PATH}`, contentType: CONTENT_TYPES.calcChain });
5335
+ }
3124
5336
  let drawingCounter = 0;
3125
5337
  let partCounter = 0;
5338
+ let commentsCounter = 0;
3126
5339
  for (let i = 0; i < __privateGet(this, _sheets).length; i++) {
3127
5340
  const ws = __privateGet(this, _sheets)[i].worksheet;
3128
5341
  const sheetRels = ws.preservedRelationships.map(
3129
- (r) => ` <Relationship Id="${r.id}" Type="${r.type}" Target="${r.target}"${r.external ? ' TargetMode="External"' : ""}/>`
5342
+ (r) => ` <Relationship Id="${r.id}" Type="${r.type}" Target="${escapeXml(r.target)}"${r.external ? ' TargetMode="External"' : ""}/>`
3130
5343
  );
5344
+ const generated = ws.generatedRels();
5345
+ for (const link of ws.hyperlinks) {
5346
+ const relId = generated.hyperlinks.get(link.ref);
5347
+ if (!relId || !link.target) continue;
5348
+ sheetRels.push(
5349
+ ` <Relationship Id="${relId}" Type="${REL_TYPES.hyperlink}" Target="${escapeXml(link.target)}" TargetMode="External"/>`
5350
+ );
5351
+ }
5352
+ if (generated.comments && generated.vmlDrawing) {
5353
+ do {
5354
+ commentsCounter++;
5355
+ } while (preservedPaths.has(`xl/comments${commentsCounter}.xml`) || preservedPaths.has(`xl/drawings/vmlDrawing${commentsCounter}.vml`));
5356
+ const commentsPath = `xl/comments${commentsCounter}.xml`;
5357
+ const vmlPath = `xl/drawings/vmlDrawing${commentsCounter}.vml`;
5358
+ entries.set(commentsPath, ws.buildCommentsXml());
5359
+ entries.set(vmlPath, ws.buildCommentsVml());
5360
+ extraOverrides.push({ partName: `/${commentsPath}`, contentType: CONTENT_TYPES.comments });
5361
+ if (!extraDefaults.some((d) => d.extension === "vml")) {
5362
+ extraDefaults.push({ extension: "vml", contentType: CONTENT_TYPES.vmlDrawing });
5363
+ }
5364
+ sheetRels.push(
5365
+ ` <Relationship Id="${generated.comments}" Type="${REL_TYPES.comments}" Target="../comments${commentsCounter}.xml"/>`,
5366
+ ` <Relationship Id="${generated.vmlDrawing}" Type="${REL_TYPES.vmlDrawing}" Target="../drawings/vmlDrawing${commentsCounter}.vml"/>`
5367
+ );
5368
+ }
3131
5369
  if (ws.hasDrawings) {
3132
5370
  do {
3133
5371
  drawingCounter++;
@@ -3164,12 +5402,48 @@ buildZipEntries_fn = function() {
3164
5402
  buildContentTypes(
3165
5403
  sheetCount,
3166
5404
  [...preservedCt.overrides, ...extraOverrides],
3167
- preservedCt.defaults
5405
+ [...preservedCt.defaults, ...extraDefaults]
3168
5406
  )
3169
5407
  );
3170
5408
  return entries;
3171
5409
  };
3172
5410
  var Workbook = _Workbook;
5411
+ var CALC_CHAIN_PATH = "xl/calcChain.xml";
5412
+ var EMPTY_CALC_CHAIN = [];
5413
+ function parseCalcChain(xml2, sheets) {
5414
+ const nameById = new Map(sheets.map((e) => [e.sheetId, e.worksheet.name]));
5415
+ const out = [];
5416
+ let currentId;
5417
+ try {
5418
+ const root = parseXml(xml2);
5419
+ for (const el of getElementsByTagName(root, "c")) {
5420
+ const ref = getAttr(el, "r");
5421
+ if (!ref) continue;
5422
+ const iAttr = getAttr(el, "i");
5423
+ if (iAttr !== void 0) {
5424
+ const parsed = parseInt(iAttr, 10);
5425
+ if (!Number.isNaN(parsed)) currentId = parsed;
5426
+ }
5427
+ if (currentId === void 0) continue;
5428
+ const sheet = nameById.get(currentId);
5429
+ if (sheet === void 0) continue;
5430
+ out.push({ sheet, ref: ref.toUpperCase() });
5431
+ }
5432
+ } catch {
5433
+ return [];
5434
+ }
5435
+ return out;
5436
+ }
5437
+ function buildCalcChainXml(chain, idBySheet) {
5438
+ const cells = [];
5439
+ for (const { sheet, ref } of chain) {
5440
+ const id = idBySheet.get(sheet);
5441
+ if (id === void 0) continue;
5442
+ cells.push(`<c r="${ref}" i="${id}"/>`);
5443
+ }
5444
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
5445
+ <calcChain xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">${cells.join("")}</calcChain>`;
5446
+ }
3173
5447
  function resolveWorksheetPath(relsXml, relId) {
3174
5448
  if (!relsXml) return void 0;
3175
5449
  try {
@@ -3185,6 +5459,6 @@ function resolveWorksheetPath(relsXml, relId) {
3185
5459
  return void 0;
3186
5460
  }
3187
5461
 
3188
- export { CONTENT_TYPES, Cell, CellStyle, DefinedNameManager, ExcelAlignment, ExcelBorder, ExcelBorderEdge, ExcelBorderStyle, ExcelColor, ExcelFill, ExcelFont, ExcelHorizontalAlignment, ExcelNumberFormat, ExcelPatternType, ExcelReadingOrder, ExcelUnderline, ExcelVerticalAlignRun, ExcelVerticalAlignment, NS, REL_TYPES, Range, StyleManager, WORKSHEET_DRAWING_REL_ID, Workbook, Worksheet, XmlBuilder, buildDrawingXml, buildRelsXml, cellRefFromRowCol, columnLetter, columnNumber, decodeUtf8, encodeUtf8, escapeXml, formatNumberWithCode, fromOADate, getAttr, getElementsByTagName, isDateFormatCode, isDateFormatId, listEntries, parseCellRef, parseRangeRef, parseXml, readXmlEntry, requireXmlEntry, setBinaryEntry, setXmlEntry, toOADate, unzipXlsx, xml, zipXlsx };
5462
+ export { CONTENT_TYPES, Cell, CellStyle, DefinedNameManager, EXCEL_MAX_COLUMNS, EXCEL_MAX_ROWS, ExcelAlignment, ExcelBorder, ExcelBorderEdge, ExcelBorderStyle, ExcelColor, ExcelFill, ExcelFont, ExcelHorizontalAlignment, ExcelNumberFormat, ExcelPatternType, ExcelReadingOrder, ExcelUnderline, ExcelVerticalAlignRun, ExcelVerticalAlignment, NS, REL_TYPES, Range, StyleManager, WORKSHEET_DRAWING_REL_ID, Workbook, Worksheet, XmlBuilder, buildDrawingXml, buildRelsXml, cellRefFromRowCol, columnLetter, columnNumber, decodeUtf8, decodeXmlEntities, encodeUtf8, escapeXml, formatDateCode, formatNumberWithCode, formatRangeRef, fromOADate, getAttr, getElementsByTagName, isDateFormatCode, isDateFormatId, listEntries, parseCellRef, parseRangeRef, parseXml, readXmlEntry, requireXmlEntry, setBinaryEntry, setXmlEntry, shiftFormulaRefs, shiftIndex, shiftRangeRef, shiftSpan, shiftSqref, toOADate, unzipXlsx, xml, zipXlsx };
3189
5463
  //# sourceMappingURL=index.js.map
3190
5464
  //# sourceMappingURL=index.js.map