@devmm/puredocs-excel 1.0.4 → 1.0.6

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
@@ -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);
@@ -294,7 +298,9 @@ var REL_TYPES = {
294
298
  styles: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
295
299
  chart: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart",
296
300
  drawing: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
297
- hyperlink: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
301
+ hyperlink: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
302
+ comments: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments",
303
+ vmlDrawing: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing"
298
304
  };
299
305
  var CONTENT_TYPES = {
300
306
  workbook: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
@@ -304,23 +310,32 @@ var CONTENT_TYPES = {
304
310
  drawing: "application/vnd.openxmlformats-officedocument.drawing+xml",
305
311
  chart: "application/vnd.openxmlformats-officedocument.drawingml.chart+xml",
306
312
  relationships: "application/vnd.openxmlformats-package.relationships+xml",
307
- coreProperties: "application/vnd.openxmlformats-package.core-properties+xml"
313
+ coreProperties: "application/vnd.openxmlformats-package.core-properties+xml",
314
+ comments: "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml",
315
+ vmlDrawing: "application/vnd.openxmlformats-officedocument.vmlDrawing"
308
316
  };
309
317
 
310
318
  // src/io/ooxml-templates.ts
311
- function buildContentTypes(sheetCount, extraOverrides = []) {
319
+ function buildContentTypes(sheetCount, extraOverrides = [], extraDefaults = []) {
312
320
  const overrides = Array.from(
313
321
  { length: sheetCount },
314
322
  (_, i) => ` <Override PartName="/xl/worksheets/sheet${i + 1}.xml" ContentType="${CONTENT_TYPES.worksheet}"/>`
315
323
  );
316
- for (const { partName, contentType } of extraOverrides) {
324
+ const seen = /* @__PURE__ */ new Set();
325
+ for (const { partName, contentType } of [...extraOverrides].reverse()) {
326
+ if (seen.has(partName)) continue;
327
+ seen.add(partName);
317
328
  overrides.push(` <Override PartName="${partName}" ContentType="${contentType}"/>`);
318
329
  }
330
+ const defaults = extraDefaults.map(
331
+ ({ extension, contentType }) => ` <Default Extension="${extension}" ContentType="${contentType}"/>`
332
+ );
319
333
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
320
334
  <Types xmlns="${"http://schemas.openxmlformats.org/package/2006/content-types"}">
321
335
  <Default Extension="rels" ContentType="${CONTENT_TYPES.relationships}"/>
322
336
  <Default Extension="xml" ContentType="application/xml"/>
323
- <Override PartName="/xl/workbook.xml" ContentType="${CONTENT_TYPES.workbook}"/>
337
+ ${defaults.length > 0 ? `${defaults.join("\n")}
338
+ ` : ""} <Override PartName="/xl/workbook.xml" ContentType="${CONTENT_TYPES.workbook}"/>
324
339
  <Override PartName="/xl/styles.xml" ContentType="${CONTENT_TYPES.styles}"/>
325
340
  <Override PartName="/xl/sharedStrings.xml" ContentType="${CONTENT_TYPES.sharedStrings}"/>
326
341
  ${overrides.join("\n")}
@@ -399,6 +414,95 @@ ${relationships.join("\n")}
399
414
  </Relationships>`;
400
415
  }
401
416
 
417
+ // src/io/preserved-parts.ts
418
+ function isCoreOwned(path) {
419
+ return path === "[Content_Types].xml" || path === "_rels/.rels" || path === "xl/workbook.xml" || path === "xl/_rels/workbook.xml.rels" || path === "xl/styles.xml" || path === "xl/sharedStrings.xml" || path.startsWith("xl/worksheets/");
420
+ }
421
+ function collectPreservedParts(entries) {
422
+ const preserved = /* @__PURE__ */ new Map();
423
+ for (const [path, bytes] of entries) {
424
+ if (!isCoreOwned(path)) preserved.set(path, bytes);
425
+ }
426
+ return preserved;
427
+ }
428
+ function parseContentTypes(xml2) {
429
+ const defaults = /* @__PURE__ */ new Map();
430
+ const overrides = /* @__PURE__ */ new Map();
431
+ for (const m of xml2.matchAll(/<Default\s+[^>]*\/?>/g)) {
432
+ const ext = /Extension="([^"]*)"/.exec(m[0])?.[1];
433
+ const ct = /ContentType="([^"]*)"/.exec(m[0])?.[1];
434
+ if (ext && ct) defaults.set(ext.toLowerCase(), ct);
435
+ }
436
+ for (const m of xml2.matchAll(/<Override\s+[^>]*\/?>/g)) {
437
+ const part = /PartName="([^"]*)"/.exec(m[0])?.[1];
438
+ const ct = /ContentType="([^"]*)"/.exec(m[0])?.[1];
439
+ if (part && ct) overrides.set(part, ct);
440
+ }
441
+ return { defaults, overrides };
442
+ }
443
+ function resolveTarget(target, baseDir) {
444
+ if (target.startsWith("/")) return target.slice(1);
445
+ const segments = baseDir.split("/").filter(Boolean);
446
+ for (const seg of target.split("/")) {
447
+ if (seg === "." || seg === "") continue;
448
+ if (seg === "..") segments.pop();
449
+ else segments.push(seg);
450
+ }
451
+ return segments.join("/");
452
+ }
453
+ function parseRelationships(relsXml, ownerPath) {
454
+ const baseDir = ownerPath.slice(0, ownerPath.lastIndexOf("/"));
455
+ const rels = [];
456
+ for (const m of relsXml.matchAll(/<Relationship\s+[^>]*\/?>/g)) {
457
+ const tag = m[0];
458
+ const id = /\bId="([^"]*)"/.exec(tag)?.[1];
459
+ const type = /\bType="([^"]*)"/.exec(tag)?.[1];
460
+ const rawTarget = /\bTarget="([^"]*)"/.exec(tag)?.[1];
461
+ if (!id || !type || rawTarget === void 0) continue;
462
+ const target = decodeXmlEntities(rawTarget);
463
+ const external = /TargetMode="External"/.test(tag);
464
+ rels.push({
465
+ id,
466
+ type,
467
+ target,
468
+ resolved: external ? void 0 : resolveTarget(target, baseDir),
469
+ external
470
+ });
471
+ }
472
+ return rels;
473
+ }
474
+ function collectSubtree(parts, startPath) {
475
+ const seen = /* @__PURE__ */ new Set();
476
+ const queue = [startPath];
477
+ while (queue.length > 0) {
478
+ const path = queue.pop();
479
+ if (seen.has(path) || !parts.has(path)) continue;
480
+ seen.add(path);
481
+ const dir = path.slice(0, path.lastIndexOf("/"));
482
+ const file = path.slice(path.lastIndexOf("/") + 1);
483
+ const relsPath = `${dir}/_rels/${file}.rels`;
484
+ const relsBytes = parts.get(relsPath);
485
+ if (!relsBytes) continue;
486
+ seen.add(relsPath);
487
+ for (const rel of parseRelationships(decodeUtf8(relsBytes), path)) {
488
+ if (rel.resolved) queue.push(rel.resolved);
489
+ }
490
+ }
491
+ return seen;
492
+ }
493
+ var PRESERVED_TAIL_TAGS = ["drawing", "legacyDrawing", "legacyDrawingHF", "picture"];
494
+ function parseTailElements(worksheetXml) {
495
+ const found = [];
496
+ for (const tag of PRESERVED_TAIL_TAGS) {
497
+ const re = new RegExp(`<(?:\\w+:)?${tag}\\s[^>]*?/>|<(?:\\w+:)?${tag}\\s[^>]*?>\\s*</(?:\\w+:)?${tag}>`, "g");
498
+ for (const m of worksheetXml.matchAll(re)) {
499
+ const relId = /r:id="([^"]*)"/.exec(m[0])?.[1];
500
+ if (relId) found.push({ tag, relId, xml: m[0] });
501
+ }
502
+ }
503
+ return found;
504
+ }
505
+
402
506
  // src/model/shared-string-manager.ts
403
507
  var _stringToIndex, _indexToString;
404
508
  var SharedStringManager = class {
@@ -1548,12 +1652,305 @@ function escAttr(s) {
1548
1652
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1549
1653
  }
1550
1654
 
1655
+ // src/style/number-format-renderer.ts
1656
+ function splitSections(code) {
1657
+ const sections = [];
1658
+ let current = "";
1659
+ let inQuote = false;
1660
+ for (let i = 0; i < code.length; i++) {
1661
+ const ch = code[i];
1662
+ if (ch === '"') {
1663
+ inQuote = !inQuote;
1664
+ current += ch;
1665
+ continue;
1666
+ }
1667
+ if (ch === "\\" && i + 1 < code.length) {
1668
+ current += ch + code[++i];
1669
+ continue;
1670
+ }
1671
+ if (ch === ";" && !inQuote) {
1672
+ sections.push(current);
1673
+ current = "";
1674
+ continue;
1675
+ }
1676
+ current += ch;
1677
+ }
1678
+ sections.push(current);
1679
+ return sections;
1680
+ }
1681
+ function hasDateTokens(section) {
1682
+ const bare = section.replace(/"[^"]*"/g, "").replace(/\\./g, "").replace(/\[[^\]]*\]/g, "");
1683
+ return /[ymdhsYMDHS]/.test(bare);
1684
+ }
1685
+ function isUnsupported(section) {
1686
+ if (/\[[<>=]/.test(section)) return true;
1687
+ if (/[?]/.test(section)) return true;
1688
+ if (/[Ee][+-]/.test(section)) return true;
1689
+ if (hasDateTokens(section)) return true;
1690
+ return false;
1691
+ }
1692
+ function parseSection(section) {
1693
+ const stripped = section.replace(
1694
+ /\[(\$([^\]-]*)[^\]]*|[^\]]*)\]/g,
1695
+ (_m, _all, currency) => typeof currency === "string" ? currency : ""
1696
+ );
1697
+ let prefix = "";
1698
+ let suffix = "";
1699
+ let pattern = "";
1700
+ let seenPattern = false;
1701
+ let percentScale = 1;
1702
+ for (let i = 0; i < stripped.length; i++) {
1703
+ const ch = stripped[i];
1704
+ if (ch === '"') {
1705
+ const end = stripped.indexOf('"', i + 1);
1706
+ const literal = stripped.slice(i + 1, end < 0 ? void 0 : end);
1707
+ if (seenPattern) suffix += literal;
1708
+ else prefix += literal;
1709
+ i = end < 0 ? stripped.length : end;
1710
+ continue;
1711
+ }
1712
+ if (ch === "\\" && i + 1 < stripped.length) {
1713
+ const literal = stripped[++i];
1714
+ if (seenPattern) suffix += literal;
1715
+ else prefix += literal;
1716
+ continue;
1717
+ }
1718
+ if (ch === "_" || ch === "*") {
1719
+ i++;
1720
+ continue;
1721
+ }
1722
+ if (ch === "#" || ch === "0" || ch === "." || ch === ",") {
1723
+ pattern += ch;
1724
+ seenPattern = true;
1725
+ continue;
1726
+ }
1727
+ if (ch === "%") {
1728
+ percentScale *= 100;
1729
+ if (seenPattern) suffix += ch;
1730
+ else prefix += ch;
1731
+ continue;
1732
+ }
1733
+ if (seenPattern) suffix += ch;
1734
+ else prefix += ch;
1735
+ }
1736
+ const dot = pattern.indexOf(".");
1737
+ const intPart = dot < 0 ? pattern : pattern.slice(0, dot);
1738
+ const fracPart = dot < 0 ? "" : pattern.slice(dot + 1);
1739
+ return {
1740
+ literalOnly: !seenPattern,
1741
+ prefix,
1742
+ suffix,
1743
+ minIntDigits: (intPart.match(/0/g) ?? []).length,
1744
+ decimals: (fracPart.match(/[#0]/g) ?? []).length,
1745
+ useGrouping: intPart.includes(","),
1746
+ percentScale
1747
+ };
1748
+ }
1749
+ function renderNumber(value, p) {
1750
+ if (p.literalOnly) return p.prefix + p.suffix;
1751
+ const scaled = value * p.percentScale;
1752
+ const fixed = scaled.toFixed(p.decimals);
1753
+ let [intDigits = "0", fracDigits = ""] = fixed.split(".");
1754
+ if (intDigits.length < p.minIntDigits) {
1755
+ intDigits = intDigits.padStart(p.minIntDigits, "0");
1756
+ }
1757
+ if (p.useGrouping) {
1758
+ intDigits = intDigits.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
1759
+ }
1760
+ return p.prefix + intDigits + (fracDigits ? `.${fracDigits}` : "") + p.suffix;
1761
+ }
1762
+ function formatNumberWithCode(value, formatCode) {
1763
+ if (!Number.isFinite(value)) return null;
1764
+ if (!formatCode || formatCode === "General") return null;
1765
+ if (formatCode === "@") return String(value);
1766
+ const sections = splitSections(formatCode);
1767
+ const negative = sections[1];
1768
+ const zero = sections[2];
1769
+ let section;
1770
+ let magnitude = value;
1771
+ if (value === 0 && zero !== void 0) {
1772
+ section = zero;
1773
+ } else if (value < 0 && negative !== void 0) {
1774
+ section = negative;
1775
+ magnitude = Math.abs(value);
1776
+ } else {
1777
+ section = sections[0];
1778
+ }
1779
+ if (section === void 0 || isUnsupported(section)) return null;
1780
+ return renderNumber(magnitude, parseSection(section));
1781
+ }
1782
+
1783
+ // src/style/date-format-renderer.ts
1784
+ var MONTHS = [
1785
+ "January",
1786
+ "February",
1787
+ "March",
1788
+ "April",
1789
+ "May",
1790
+ "June",
1791
+ "July",
1792
+ "August",
1793
+ "September",
1794
+ "October",
1795
+ "November",
1796
+ "December"
1797
+ ];
1798
+ var DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
1799
+ function firstSection(code) {
1800
+ let inQuote = false;
1801
+ for (let i = 0; i < code.length; i++) {
1802
+ const ch = code[i];
1803
+ if (ch === '"') inQuote = !inQuote;
1804
+ else if (ch === ";" && !inQuote) return code.slice(0, i);
1805
+ }
1806
+ return code;
1807
+ }
1808
+ function tokenize(section) {
1809
+ const tokens = [];
1810
+ let i = 0;
1811
+ const n = section.length;
1812
+ while (i < n) {
1813
+ const ch = section[i];
1814
+ if (ch === '"') {
1815
+ let j = i + 1;
1816
+ let text = "";
1817
+ while (j < n && section[j] !== '"') text += section[j++];
1818
+ tokens.push({ kind: "literal", text });
1819
+ i = j + 1;
1820
+ continue;
1821
+ }
1822
+ if (ch === "\\") {
1823
+ tokens.push({ kind: "literal", text: section[i + 1] ?? "" });
1824
+ i += 2;
1825
+ continue;
1826
+ }
1827
+ if (ch === "[") {
1828
+ const close = section.indexOf("]", i);
1829
+ if (close === -1) return null;
1830
+ const inner = section.slice(i + 1, close);
1831
+ if (/^[hmsHMS]+$/.test(inner)) return null;
1832
+ i = close + 1;
1833
+ continue;
1834
+ }
1835
+ const rest = section.slice(i);
1836
+ const ampm = /^(AM\/PM|A\/P|am\/pm|a\/p)/.exec(rest);
1837
+ if (ampm) {
1838
+ tokens.push({ kind: "ampm", text: ampm[1] });
1839
+ i += ampm[1].length;
1840
+ continue;
1841
+ }
1842
+ const lower = ch.toLowerCase();
1843
+ if ("ymdhs".includes(lower)) {
1844
+ let j = i;
1845
+ while (j < n && section[j].toLowerCase() === lower) j++;
1846
+ tokens.push({ kind: lower, text: section.slice(i, j) });
1847
+ i = j;
1848
+ if (lower === "s" && section[i] === "." && /\d|0/.test(section[i + 1] ?? "")) return null;
1849
+ continue;
1850
+ }
1851
+ if (/[a-zA-Z]/.test(ch)) return null;
1852
+ tokens.push({ kind: "literal", text: ch });
1853
+ i++;
1854
+ }
1855
+ return tokens;
1856
+ }
1857
+ function resolveMonthVsMinute(tokens) {
1858
+ const dateKinds = /* @__PURE__ */ new Set(["y", "m", "d", "h", "s"]);
1859
+ for (let t = 0; t < tokens.length; t++) {
1860
+ const token = tokens[t];
1861
+ if (token.kind !== "m" || token.text.length > 2) continue;
1862
+ let prev;
1863
+ for (let p = t - 1; p >= 0; p--) {
1864
+ if (dateKinds.has(tokens[p].kind)) {
1865
+ prev = tokens[p];
1866
+ break;
1867
+ }
1868
+ }
1869
+ let next;
1870
+ for (let q = t + 1; q < tokens.length; q++) {
1871
+ if (dateKinds.has(tokens[q].kind)) {
1872
+ next = tokens[q];
1873
+ break;
1874
+ }
1875
+ }
1876
+ if (prev?.kind === "h" || next?.kind === "s") token.kind = "minute";
1877
+ }
1878
+ }
1879
+ var pad = (v, len) => String(v).padStart(len, "0");
1880
+ function formatDateCode(date, formatCode) {
1881
+ if (!formatCode || formatCode === "General") return null;
1882
+ const tokens = tokenize(firstSection(formatCode));
1883
+ if (!tokens) return null;
1884
+ if (!tokens.some((t) => "ymdhs".includes(t.kind))) return null;
1885
+ resolveMonthVsMinute(tokens);
1886
+ const twelveHour = tokens.some((t) => t.kind === "ampm");
1887
+ const hours24 = date.getHours();
1888
+ const hours12 = hours24 % 12 === 0 ? 12 : hours24 % 12;
1889
+ let out = "";
1890
+ for (const t of tokens) {
1891
+ switch (t.kind) {
1892
+ case "literal":
1893
+ out += t.text;
1894
+ break;
1895
+ case "y":
1896
+ out += t.text.length <= 2 ? pad(date.getFullYear() % 100, 2) : String(date.getFullYear());
1897
+ break;
1898
+ case "m": {
1899
+ const month = date.getMonth();
1900
+ if (t.text.length === 1) out += String(month + 1);
1901
+ else if (t.text.length === 2) out += pad(month + 1, 2);
1902
+ else if (t.text.length === 3) out += MONTHS[month].slice(0, 3);
1903
+ else if (t.text.length === 5) out += MONTHS[month][0];
1904
+ else out += MONTHS[month];
1905
+ break;
1906
+ }
1907
+ case "minute":
1908
+ out += t.text.length === 1 ? String(date.getMinutes()) : pad(date.getMinutes(), 2);
1909
+ break;
1910
+ case "d": {
1911
+ const day = date.getDate();
1912
+ if (t.text.length === 1) out += String(day);
1913
+ else if (t.text.length === 2) out += pad(day, 2);
1914
+ else if (t.text.length === 3) out += DAYS[date.getDay()].slice(0, 3);
1915
+ else out += DAYS[date.getDay()];
1916
+ break;
1917
+ }
1918
+ case "h": {
1919
+ const h = twelveHour ? hours12 : hours24;
1920
+ out += t.text.length === 1 ? String(h) : pad(h, 2);
1921
+ break;
1922
+ }
1923
+ case "s":
1924
+ out += t.text.length === 1 ? String(date.getSeconds()) : pad(date.getSeconds(), 2);
1925
+ break;
1926
+ case "ampm": {
1927
+ const isAm = hours24 < 12;
1928
+ if (t.text === "a/p") out += isAm ? "a" : "p";
1929
+ else if (t.text === "A/P") out += isAm ? "A" : "P";
1930
+ else if (t.text === "am/pm") out += isAm ? "am" : "pm";
1931
+ else out += isAm ? "AM" : "PM";
1932
+ break;
1933
+ }
1934
+ }
1935
+ }
1936
+ return out;
1937
+ }
1938
+
1551
1939
  // src/model/cell-reference.ts
1940
+ var EXCEL_MAX_ROWS = 1048576;
1941
+ var EXCEL_MAX_COLUMNS = 16384;
1942
+ function needsNormalizing(ref) {
1943
+ for (let i = 0; i < ref.length; i++) {
1944
+ const code = ref.charCodeAt(i);
1945
+ if (code === 36 || code >= 97 && code <= 122) return true;
1946
+ }
1947
+ return false;
1948
+ }
1552
1949
  function parseCellRef(cellReference) {
1553
1950
  if (!cellReference) {
1554
1951
  throw new Error("Cell reference cannot be null or empty.");
1555
1952
  }
1556
- const ref = cellReference.replace(/\$/g, "").toUpperCase();
1953
+ const ref = needsNormalizing(cellReference) ? cellReference.replace(/\$/g, "").toUpperCase() : cellReference;
1557
1954
  let i = 0;
1558
1955
  let column = 0;
1559
1956
  while (i < ref.length && ref.charCodeAt(i) >= 65 && ref.charCodeAt(i) <= 90) {
@@ -1599,6 +1996,11 @@ function columnNumber(letters) {
1599
1996
  }
1600
1997
  return col;
1601
1998
  }
1999
+ function formatRangeRef(startRow, startColumn, endRow, endColumn) {
2000
+ const start = cellRefFromRowCol(startRow, startColumn);
2001
+ if (startRow === endRow && startColumn === endColumn) return start;
2002
+ return `${start}:${cellRefFromRowCol(endRow, endColumn)}`;
2003
+ }
1602
2004
  function parseRangeRef(rangeReference) {
1603
2005
  const parts = rangeReference.split(":");
1604
2006
  if (parts.length !== 2) {
@@ -1747,6 +2149,9 @@ var Cell = class {
1747
2149
  }
1748
2150
  /**
1749
2151
  * Returns the display text of the cell value (always a string).
2152
+ *
2153
+ * Numbers are returned unformatted; use {@link getFormattedText} to apply the
2154
+ * cell's number format.
1750
2155
  */
1751
2156
  getText() {
1752
2157
  const val = this.getValue();
@@ -1754,6 +2159,23 @@ var Cell = class {
1754
2159
  if (val instanceof Date) return val.toLocaleDateString();
1755
2160
  return String(val);
1756
2161
  }
2162
+ /**
2163
+ * Returns the display text with the cell's number format applied, e.g. a
2164
+ * value of 4.5 under `$#,##0.00` renders as `$4.50`, and a date under
2165
+ * `dd/mm/yyyy` as `31/01/2025`.
2166
+ *
2167
+ * Falls back to {@link getText} for values with no format and for format
2168
+ * codes outside the supported subset (fractions, scientific notation,
2169
+ * conditional sections, elapsed time) — so the result is never a misleading
2170
+ * rendering.
2171
+ */
2172
+ getFormattedText() {
2173
+ const val = this.getValue();
2174
+ if (typeof val !== "number" && !(val instanceof Date)) return this.getText();
2175
+ const format = __privateGet(this, _styles).getCellStyle(__privateGet(this, _data).styleIndex).numberFormat;
2176
+ if (!format) return this.getText();
2177
+ return val instanceof Date ? formatDateCode(val, format.formatCode) ?? this.getText() : formatNumberWithCode(val, format.formatCode) ?? this.getText();
2178
+ }
1757
2179
  // ── Formula API ────────────────────────────────────────────────────────────
1758
2180
  /**
1759
2181
  * Sets a formula. The leading '=' is optional.
@@ -1937,8 +2359,269 @@ function escXml(s) {
1937
2359
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1938
2360
  }
1939
2361
 
2362
+ // src/model/ref-shift.ts
2363
+ function shiftIndex(index, shift) {
2364
+ if (shift.kind === "insert") {
2365
+ return index >= shift.at ? index + shift.count : index;
2366
+ }
2367
+ const bandEnd = shift.at + shift.count - 1;
2368
+ if (index < shift.at) return index;
2369
+ if (index <= bandEnd) return null;
2370
+ return index - shift.count;
2371
+ }
2372
+ function shiftSpan(start, end, shift) {
2373
+ if (shift.kind === "insert") {
2374
+ return {
2375
+ start: start >= shift.at ? start + shift.count : start,
2376
+ end: end >= shift.at ? end + shift.count : end
2377
+ };
2378
+ }
2379
+ const bandEnd = shift.at + shift.count - 1;
2380
+ const newStart = start < shift.at ? start : start <= bandEnd ? shift.at : start - shift.count;
2381
+ const newEnd = end < shift.at ? end : end <= bandEnd ? shift.at - 1 : end - shift.count;
2382
+ return newEnd < newStart ? null : { start: newStart, end: newEnd };
2383
+ }
2384
+ function shiftRangeRef(ref, shift) {
2385
+ const [startRef, endRef] = ref.split(":");
2386
+ const start = parseCellRef(startRef);
2387
+ const end = endRef !== void 0 ? parseCellRef(endRef) : start;
2388
+ const rows = shift.dim === "row" ? shiftSpan(start.row, end.row, shift) : { start: start.row, end: end.row };
2389
+ const cols = shift.dim === "col" ? shiftSpan(start.column, end.column, shift) : { start: start.column, end: end.column };
2390
+ if (!rows || !cols) return null;
2391
+ const startOut = cellRefFromRowCol(rows.start, cols.start);
2392
+ if (endRef === void 0) return startOut;
2393
+ return `${startOut}:${cellRefFromRowCol(rows.end, cols.end)}`;
2394
+ }
2395
+ function shiftSqref(sqref, shift) {
2396
+ const surviving = sqref.split(/\s+/).filter(Boolean).map((part) => shiftRangeRef(part, shift)).filter((part) => part !== null);
2397
+ return surviving.length > 0 ? surviving.join(" ") : null;
2398
+ }
2399
+
2400
+ // src/model/formula-ref-shift.ts
2401
+ var MAX_ROW = EXCEL_MAX_ROWS;
2402
+ var MAX_COL = EXCEL_MAX_COLUMNS;
2403
+ function isWordChar(code) {
2404
+ return code >= 65 && code <= 90 || // A-Z
2405
+ code >= 97 && code <= 122 || // a-z
2406
+ code >= 48 && code <= 57 || // 0-9
2407
+ code === 95 || // _
2408
+ code === 46 || // .
2409
+ code === 36;
2410
+ }
2411
+ function parseCellComponent(word) {
2412
+ const m = /^(\$?)([A-Za-z]{1,3})(\$?)(\d+)$/.exec(word);
2413
+ if (!m) return null;
2414
+ const col = columnNumber(m[2]);
2415
+ const row = parseInt(m[4], 10);
2416
+ if (col > MAX_COL || row < 1 || row > MAX_ROW) return null;
2417
+ return { colAbs: m[1] === "$", col, rowAbs: m[3] === "$", row };
2418
+ }
2419
+ function parseColComponent(word) {
2420
+ const m = /^(\$?)([A-Za-z]{1,3})$/.exec(word);
2421
+ if (!m) return null;
2422
+ const col = columnNumber(m[2]);
2423
+ if (col > MAX_COL) return null;
2424
+ return { abs: m[1] === "$", col };
2425
+ }
2426
+ function parseRowComponent(word) {
2427
+ const m = /^(\$?)(\d+)$/.exec(word);
2428
+ if (!m) return null;
2429
+ const row = parseInt(m[2], 10);
2430
+ if (row < 1 || row > MAX_ROW) return null;
2431
+ return { abs: m[1] === "$", row };
2432
+ }
2433
+ function emitCell(c, row, col) {
2434
+ return `${c.colAbs ? "$" : ""}${columnLetter(col)}${c.rowAbs ? "$" : ""}${row}`;
2435
+ }
2436
+ function shiftFormulaRefs(formula, shift, formulaSheet) {
2437
+ const n = formula.length;
2438
+ const editedSheet = shift.sheetName.toUpperCase();
2439
+ const unqualifiedRewritable = formulaSheet.toUpperCase() === editedSheet;
2440
+ const isRowShift = shift.dim === "row";
2441
+ let out = "";
2442
+ let flushed = 0;
2443
+ let changed = false;
2444
+ let hasRefError = false;
2445
+ let i = 0;
2446
+ const replace = (start, text) => {
2447
+ if (text.length === i - start && formula.startsWith(text, start)) return;
2448
+ out += formula.slice(flushed, start) + text;
2449
+ flushed = i;
2450
+ changed = true;
2451
+ };
2452
+ const readWord = () => {
2453
+ let j = i;
2454
+ while (j < n && isWordChar(formula.charCodeAt(j))) j++;
2455
+ const w = formula.slice(i, j);
2456
+ i = j;
2457
+ return w;
2458
+ };
2459
+ const readQuoted = () => {
2460
+ let j = i + 1;
2461
+ while (j < n) {
2462
+ if (formula[j] === "'") {
2463
+ if (formula[j + 1] === "'") {
2464
+ j += 2;
2465
+ continue;
2466
+ }
2467
+ j++;
2468
+ break;
2469
+ }
2470
+ j++;
2471
+ }
2472
+ const q = formula.slice(i, j);
2473
+ i = j;
2474
+ return q;
2475
+ };
2476
+ const consumeRef = (refStart, rewritable) => {
2477
+ const first = readWord();
2478
+ let firstCell = parseCellComponent(first);
2479
+ let firstCol = firstCell ? null : parseColComponent(first);
2480
+ let firstRow = firstCell || firstCol ? null : parseRowComponent(first);
2481
+ let second = null;
2482
+ let secondCell = null;
2483
+ let secondCol = null;
2484
+ let secondRow = null;
2485
+ if (formula[i] === ":" && i + 1 < n && isWordChar(formula.charCodeAt(i + 1))) {
2486
+ const save = i;
2487
+ i++;
2488
+ const w = readWord();
2489
+ if (firstCell) secondCell = parseCellComponent(w);
2490
+ else if (firstCol) secondCol = parseColComponent(w);
2491
+ else if (firstRow) secondRow = parseRowComponent(w);
2492
+ if (secondCell || secondCol || secondRow) {
2493
+ second = w;
2494
+ } else {
2495
+ i = save;
2496
+ }
2497
+ }
2498
+ if (second === null && formula[i] === "(") return;
2499
+ const dead = () => {
2500
+ if (formula[i] === "#") i++;
2501
+ replace(refStart, "#REF!");
2502
+ hasRefError = true;
2503
+ };
2504
+ if (second !== null) {
2505
+ if (!rewritable) return;
2506
+ if (firstCell && secondCell) {
2507
+ const a = firstCell, b = secondCell;
2508
+ const rows = isRowShift ? shiftSpan(a.row, b.row, shift) : { start: a.row, end: b.row };
2509
+ const cols = isRowShift ? { start: a.col, end: b.col } : shiftSpan(a.col, b.col, shift);
2510
+ if (!rows || !cols) {
2511
+ dead();
2512
+ return;
2513
+ }
2514
+ replace(refStart, `${emitCell(a, rows.start, cols.start)}:${emitCell(b, rows.end, cols.end)}`);
2515
+ return;
2516
+ }
2517
+ if (firstCol && secondCol) {
2518
+ if (isRowShift) return;
2519
+ const span = shiftSpan(firstCol.col, secondCol.col, shift);
2520
+ if (!span) {
2521
+ dead();
2522
+ return;
2523
+ }
2524
+ const a = firstCol.abs ? "$" : "";
2525
+ const b = secondCol.abs ? "$" : "";
2526
+ replace(refStart, `${a}${columnLetter(span.start)}:${b}${columnLetter(span.end)}`);
2527
+ return;
2528
+ }
2529
+ if (firstRow && secondRow) {
2530
+ if (!isRowShift) return;
2531
+ const span = shiftSpan(firstRow.row, secondRow.row, shift);
2532
+ if (!span) {
2533
+ dead();
2534
+ return;
2535
+ }
2536
+ const a = firstRow.abs ? "$" : "";
2537
+ const b = secondRow.abs ? "$" : "";
2538
+ replace(refStart, `${a}${span.start}:${b}${span.end}`);
2539
+ return;
2540
+ }
2541
+ return;
2542
+ }
2543
+ if (!firstCell || !rewritable) return;
2544
+ const row = isRowShift ? shiftIndex(firstCell.row, shift) : firstCell.row;
2545
+ const col = isRowShift ? firstCell.col : shiftIndex(firstCell.col, shift);
2546
+ if (row === null || col === null) {
2547
+ dead();
2548
+ return;
2549
+ }
2550
+ replace(refStart, emitCell(firstCell, row, col));
2551
+ };
2552
+ while (i < n) {
2553
+ const ch = formula[i];
2554
+ if (ch === '"') {
2555
+ let j = i + 1;
2556
+ while (j < n) {
2557
+ if (formula[j] === '"') {
2558
+ if (formula[j + 1] === '"') {
2559
+ j += 2;
2560
+ continue;
2561
+ }
2562
+ j++;
2563
+ break;
2564
+ }
2565
+ j++;
2566
+ }
2567
+ i = j;
2568
+ continue;
2569
+ }
2570
+ if (ch === "'") {
2571
+ const quoted = readQuoted();
2572
+ if (formula[i] === "!") {
2573
+ i++;
2574
+ const name = quoted.slice(1, -1);
2575
+ const rewritable = name.indexOf(":") === -1 && (name.indexOf("''") === -1 ? name : name.replace(/''/g, "'")).toUpperCase() === editedSheet;
2576
+ consumeRef(i, rewritable);
2577
+ }
2578
+ continue;
2579
+ }
2580
+ if (isWordChar(formula.charCodeAt(i))) {
2581
+ const start = i;
2582
+ const word = readWord();
2583
+ if (formula[i] === "!") {
2584
+ i++;
2585
+ consumeRef(i, word.toUpperCase() === editedSheet);
2586
+ continue;
2587
+ }
2588
+ if (formula[i] === ":") {
2589
+ const save = i;
2590
+ i++;
2591
+ const w2 = i < n && isWordChar(formula.charCodeAt(i)) ? readWord() : "";
2592
+ if (w2 && formula[i] === "!") {
2593
+ i++;
2594
+ consumeRef(i, false);
2595
+ continue;
2596
+ }
2597
+ i = save;
2598
+ }
2599
+ if (formula[i] === "[") {
2600
+ let depth = 0;
2601
+ let j = i;
2602
+ while (j < n) {
2603
+ if (formula[j] === "[") depth++;
2604
+ else if (formula[j] === "]" && --depth === 0) {
2605
+ j++;
2606
+ break;
2607
+ }
2608
+ j++;
2609
+ }
2610
+ i = j;
2611
+ continue;
2612
+ }
2613
+ i = start;
2614
+ consumeRef(start, unqualifiedRewritable);
2615
+ continue;
2616
+ }
2617
+ i++;
2618
+ }
2619
+ if (!changed) return { text: formula, changed: false, hasRefError };
2620
+ return { text: out + formula.slice(flushed), changed: true, hasRefError };
2621
+ }
2622
+
1940
2623
  // src/model/range.ts
1941
- var _sheet, _startRow, _startColumn, _endRow, _endColumn, _address, _Range_instances, forEach_fn;
2624
+ var _sheet, _startRow, _startColumn, _endRow, _endColumn, _address, _Range_instances, forEach_fn, forEachExisting_fn;
1942
2625
  var Range = class {
1943
2626
  constructor(sheet, rangeReference) {
1944
2627
  __privateAdd(this, _Range_instances);
@@ -1987,27 +2670,64 @@ var Range = class {
1987
2670
  * Gets all cell values as a 2D array (row-major order).
1988
2671
  */
1989
2672
  getValues() {
1990
- const result = [];
1991
- for (let r = 0; r < this.rowCount; r++) {
1992
- const row = [];
1993
- for (let c = 0; c < this.columnCount; c++) {
1994
- const cell = __privateGet(this, _sheet).tryGetCell(__privateGet(this, _startRow) + r, __privateGet(this, _startColumn) + c);
1995
- row.push(cell ? cell.getValue() : null);
2673
+ const rowCount = this.rowCount;
2674
+ const columnCount = this.columnCount;
2675
+ const result = new Array(rowCount);
2676
+ for (let r = 0; r < rowCount; r++) result[r] = new Array(columnCount).fill(null);
2677
+ const startRow = __privateGet(this, _startRow);
2678
+ const startColumn = __privateGet(this, _startColumn);
2679
+ __privateGet(this, _sheet).forEachCellIn(
2680
+ startRow,
2681
+ startColumn,
2682
+ __privateGet(this, _endRow),
2683
+ __privateGet(this, _endColumn),
2684
+ (cell, row, column) => {
2685
+ result[row - startRow][column - startColumn] = cell.getValue();
1996
2686
  }
1997
- result.push(row);
1998
- }
2687
+ );
1999
2688
  return result;
2000
2689
  }
2001
2690
  /** Clears all cells in the range (values only, preserves style). */
2002
2691
  clear() {
2003
- __privateMethod(this, _Range_instances, forEach_fn).call(this, (cell) => cell.clear());
2692
+ __privateMethod(this, _Range_instances, forEachExisting_fn).call(this, (cell) => cell.clear());
2004
2693
  }
2005
- /** Auto-fits all columns in the range. */
2006
- autoFit() {
2007
- for (let col = __privateGet(this, _startColumn); col <= __privateGet(this, _endColumn); col++) {
2008
- __privateGet(this, _sheet).autoFitColumn(col);
2694
+ /** Clears values AND styles of all cells in the range. */
2695
+ clearAll() {
2696
+ __privateMethod(this, _Range_instances, forEachExisting_fn).call(this, (cell) => cell.clearAll());
2697
+ }
2698
+ /**
2699
+ * Moves the whole range so its top-left cell lands on `destTopLeft`,
2700
+ * carrying values, formulas, styles and spill anchors. The vacated cells
2701
+ * are cleared and the destination block is fully replaced (empty source
2702
+ * cells clear their destination). Overlapping source/destination is safe.
2703
+ * Formula text is NOT adjusted for the new position.
2704
+ *
2705
+ * @example
2706
+ * ws.getRange('A1:B3').moveTo('D1');
2707
+ */
2708
+ moveTo(destTopLeft) {
2709
+ const dest = parseCellRef(destTopLeft.replace(/\$/g, "").toUpperCase());
2710
+ const deltaRow = dest.row - __privateGet(this, _startRow);
2711
+ const deltaCol = dest.column - __privateGet(this, _startColumn);
2712
+ if (deltaRow === 0 && deltaCol === 0) return;
2713
+ if (__privateGet(this, _endRow) + deltaRow > EXCEL_MAX_ROWS || __privateGet(this, _endColumn) + deltaCol > EXCEL_MAX_COLUMNS) {
2714
+ throw new RangeError("Destination extends past the sheet limits.");
2715
+ }
2716
+ const rowOrder = deltaRow > 0 ? { from: __privateGet(this, _endRow), to: __privateGet(this, _startRow), step: -1 } : { from: __privateGet(this, _startRow), to: __privateGet(this, _endRow), step: 1 };
2717
+ const colOrder = deltaCol > 0 ? { from: __privateGet(this, _endColumn), to: __privateGet(this, _startColumn), step: -1 } : { from: __privateGet(this, _startColumn), to: __privateGet(this, _endColumn), step: 1 };
2718
+ for (let r = rowOrder.from; deltaRow > 0 ? r >= rowOrder.to : r <= rowOrder.to; r += rowOrder.step) {
2719
+ for (let c = colOrder.from; deltaCol > 0 ? c >= colOrder.to : c <= colOrder.to; c += colOrder.step) {
2720
+ __privateGet(this, _sheet).moveCellRC(r, c, r + deltaRow, c + deltaCol);
2721
+ }
2009
2722
  }
2010
2723
  }
2724
+ /**
2725
+ * Auto-fits all columns in the range. Measures every column in one sparse
2726
+ * sweep rather than re-scanning the sheet per column.
2727
+ */
2728
+ autoFit() {
2729
+ __privateGet(this, _sheet).autoFitColumns(__privateGet(this, _startColumn), __privateGet(this, _endColumn));
2730
+ }
2011
2731
  // ── Style bulk operations ──────────────────────────────────────────────────
2012
2732
  /** Applies a CellStyle to all cells in the range. Returns this for chaining. */
2013
2733
  setStyle(style) {
@@ -2080,9 +2800,57 @@ forEach_fn = function(action) {
2080
2800
  }
2081
2801
  }
2082
2802
  };
2803
+ /**
2804
+ * Like #forEach but only visits cells that already exist — used by the
2805
+ * clearing operations so they never materialise empty cells, and so the cost
2806
+ * tracks the populated cells rather than the area of the range.
2807
+ */
2808
+ forEachExisting_fn = function(action) {
2809
+ __privateGet(this, _sheet).forEachCellIn(
2810
+ __privateGet(this, _startRow),
2811
+ __privateGet(this, _startColumn),
2812
+ __privateGet(this, _endRow),
2813
+ __privateGet(this, _endColumn),
2814
+ (cell) => action(cell)
2815
+ );
2816
+ };
2083
2817
 
2084
2818
  // src/model/worksheet.ts
2085
- var _sharedStrings2, _styles2, _name, _rows, _cols, _mergeCells, _pane, _autoFilter, _dataValidations, _visibility, _drawings, _Worksheet_instances, buildColsXml_fn, buildSheetDataXml_fn, buildMergeCellsXml_fn, buildDataValidationsXml_fn, buildSheetViewXml_fn, getOrCreateRow_fn;
2819
+ var PROTECTION_DEFAULTS = {
2820
+ allowSelectLockedCells: true,
2821
+ allowSelectUnlockedCells: true,
2822
+ allowFormatCells: false,
2823
+ allowFormatColumns: false,
2824
+ allowFormatRows: false,
2825
+ allowInsertRows: false,
2826
+ allowInsertColumns: false,
2827
+ allowInsertHyperlinks: false,
2828
+ allowDeleteRows: false,
2829
+ allowDeleteColumns: false,
2830
+ allowSort: false,
2831
+ allowAutoFilter: false,
2832
+ allowPivotTables: false,
2833
+ allowEditObjects: false,
2834
+ allowEditScenarios: false
2835
+ };
2836
+ var PROTECTION_ATTRS = [
2837
+ ["allowSelectLockedCells", "selectLockedCells"],
2838
+ ["allowSelectUnlockedCells", "selectUnlockedCells"],
2839
+ ["allowFormatCells", "formatCells"],
2840
+ ["allowFormatColumns", "formatColumns"],
2841
+ ["allowFormatRows", "formatRows"],
2842
+ ["allowInsertRows", "insertRows"],
2843
+ ["allowInsertColumns", "insertColumns"],
2844
+ ["allowInsertHyperlinks", "insertHyperlinks"],
2845
+ ["allowDeleteRows", "deleteRows"],
2846
+ ["allowDeleteColumns", "deleteColumns"],
2847
+ ["allowSort", "sort"],
2848
+ ["allowAutoFilter", "autoFilter"],
2849
+ ["allowPivotTables", "pivotTables"],
2850
+ ["allowEditObjects", "objects"],
2851
+ ["allowEditScenarios", "scenarios"]
2852
+ ];
2853
+ var _sharedStrings2, _styles2, _name, _rows, _cols, _mergeCells, _pane, _autoFilter, _dataValidations, _visibility, _drawings, _protection, _protectionCredentials, _hyperlinks, _comments, _commentsDirty, _pendingHyperlinkRels, _preservedTail, _preservedRels, _Worksheet_instances, 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, buildColsXml_fn, buildSheetDataXml_fn, buildMergeCellsXml_fn, buildDataValidationsXml_fn, buildSheetViewXml_fn, splitColSpan_fn, insertColDef_fn, findColDefIndex_fn, findColDef_fn, pruneColDefs_fn, getOrCreateRow_fn;
2086
2854
  var Worksheet = class {
2087
2855
  /** @internal */
2088
2856
  constructor(name, sharedStrings, styles) {
@@ -2098,6 +2866,32 @@ var Worksheet = class {
2098
2866
  __privateAdd(this, _dataValidations, []);
2099
2867
  __privateAdd(this, _visibility, "visible");
2100
2868
  __privateAdd(this, _drawings, []);
2869
+ __privateAdd(this, _protection);
2870
+ /**
2871
+ * Password/hash attributes of a protection element read from a file, kept
2872
+ * verbatim so a protected sheet round-trips with its password intact. This
2873
+ * library never creates them: see {@link protect}.
2874
+ */
2875
+ __privateAdd(this, _protectionCredentials, "");
2876
+ /** Hyperlinks keyed by their normalised ref, in insertion order. */
2877
+ __privateAdd(this, _hyperlinks, /* @__PURE__ */ new Map());
2878
+ /** Comments keyed by their normalised cell ref, in insertion order. */
2879
+ __privateAdd(this, _comments, /* @__PURE__ */ new Map());
2880
+ /**
2881
+ * True once the comment set has been modified through the API. Until then a
2882
+ * sheet loaded from a file keeps its original comments and VML parts verbatim,
2883
+ * so untouched files round-trip byte-for-byte; from then on this sheet owns
2884
+ * them and regenerates both. See {@link setComment}.
2885
+ */
2886
+ __privateAdd(this, _commentsDirty, false);
2887
+ /** Hyperlink ref → relationship id, awaiting resolution against the rels part. */
2888
+ __privateAdd(this, _pendingHyperlinkRels, /* @__PURE__ */ new Map());
2889
+ // Round-trip state captured when this sheet was loaded from a file: the
2890
+ // relationship-bearing leaf elements of the worksheet part, and the sheet's
2891
+ // original relationships. Re-emitted on save so drawings, images and legacy
2892
+ // comment anchors survive open→save. See io/preserved-parts.ts.
2893
+ __privateAdd(this, _preservedTail, []);
2894
+ __privateAdd(this, _preservedRels, []);
2101
2895
  __privateSet(this, _name, name);
2102
2896
  __privateSet(this, _sharedStrings2, sharedStrings);
2103
2897
  __privateSet(this, _styles2, styles);
@@ -2111,8 +2905,15 @@ var Worksheet = class {
2111
2905
  __privateSet(this, _name, value);
2112
2906
  }
2113
2907
  getCell(refOrRow, column) {
2114
- const ref = typeof refOrRow === "string" ? refOrRow.replace(/\$/g, "").toUpperCase() : cellRefFromRowCol(refOrRow, column);
2115
- const { row } = parseCellRef(ref);
2908
+ let ref;
2909
+ let row;
2910
+ if (typeof refOrRow === "string") {
2911
+ ref = normalizeRefFast(refOrRow);
2912
+ row = parseCellRef(ref).row;
2913
+ } else {
2914
+ ref = cellRefFromRowCol(refOrRow, column);
2915
+ row = refOrRow;
2916
+ }
2116
2917
  const rowData = __privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, row);
2117
2918
  let cellData = rowData.cells.get(ref);
2118
2919
  if (!cellData) {
@@ -2122,10 +2923,17 @@ var Worksheet = class {
2122
2923
  return new Cell(cellData, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2));
2123
2924
  }
2124
2925
  tryGetCell(refOrRow, column) {
2125
- const ref = typeof refOrRow === "string" ? refOrRow.replace(/\$/g, "").toUpperCase() : cellRefFromRowCol(refOrRow, column);
2126
- const { row } = parseCellRef(ref);
2127
- const rowData = __privateGet(this, _rows).get(row);
2128
- if (!rowData) return null;
2926
+ let rowData;
2927
+ let ref;
2928
+ if (typeof refOrRow === "string") {
2929
+ ref = normalizeRefFast(refOrRow);
2930
+ rowData = __privateGet(this, _rows).get(parseCellRef(ref).row);
2931
+ if (!rowData) return null;
2932
+ } else {
2933
+ rowData = __privateGet(this, _rows).get(refOrRow);
2934
+ if (!rowData) return null;
2935
+ ref = cellRefFromRowCol(refOrRow, column);
2936
+ }
2129
2937
  const cellData = rowData.cells.get(ref);
2130
2938
  if (!cellData) return null;
2131
2939
  return new Cell(cellData, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2));
@@ -2150,10 +2958,83 @@ var Worksheet = class {
2150
2958
  getSpillRange(reference) {
2151
2959
  return this.tryGetCell(reference)?.getArrayRef() ?? null;
2152
2960
  }
2961
+ /**
2962
+ * Iterates the populated rows of the sheet in ascending row order, yielding
2963
+ * each row's 1-based index and its cells (left to right). Rows and cells
2964
+ * that were never written are skipped, so a sparse sheet iterates in
2965
+ * O(populated cells) regardless of its bounds.
2966
+ *
2967
+ * @example
2968
+ * for (const { rowIndex, cells } of ws.rows()) {
2969
+ * for (const cell of cells) console.log(rowIndex, cell.reference, cell.getValue());
2970
+ * }
2971
+ */
2972
+ *rows() {
2973
+ const populated = [...__privateGet(this, _rows).values()].filter((row) => row.cells.size > 0).sort((a, b) => a.rowIndex - b.rowIndex);
2974
+ for (const row of populated) {
2975
+ 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)));
2976
+ yield { rowIndex: row.rowIndex, cells };
2977
+ }
2978
+ }
2979
+ /**
2980
+ * Iterates every populated cell of the sheet in row-major order. Sparse
2981
+ * counterpart to scanning {@link usedBounds} with {@link tryGetCell}.
2982
+ *
2983
+ * @example
2984
+ * for (const cell of ws.cells()) console.log(cell.reference, cell.getValue());
2985
+ */
2986
+ *cells() {
2987
+ for (const { cells } of this.rows()) yield* cells;
2988
+ }
2989
+ /**
2990
+ * @internal Visits the populated cells inside a rectangle, passing the
2991
+ * coordinates it already knows so the callback never has to parse a
2992
+ * reference. Backs the Range operations that must not create cells; probing
2993
+ * every coordinate would cost O(area) even on an empty sheet.
2994
+ */
2995
+ forEachCellIn(startRow, startColumn, endRow, endColumn, action) {
2996
+ const visit = (row) => {
2997
+ for (const cd of row.cells.values()) {
2998
+ const col = columnFromRef(cd.reference, refSplit(cd.reference));
2999
+ if (col < startColumn || col > endColumn) continue;
3000
+ action(new Cell(cd, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2)), row.rowIndex, col);
3001
+ }
3002
+ };
3003
+ if (endRow - startRow + 1 <= __privateGet(this, _rows).size) {
3004
+ for (let r = startRow; r <= endRow; r++) {
3005
+ const row = __privateGet(this, _rows).get(r);
3006
+ if (row) visit(row);
3007
+ }
3008
+ return;
3009
+ }
3010
+ for (const row of __privateGet(this, _rows).values()) {
3011
+ if (row.rowIndex >= startRow && row.rowIndex <= endRow) visit(row);
3012
+ }
3013
+ }
3014
+ /**
3015
+ * @internal Moves a cell by coordinates, skipping the reference parsing that
3016
+ * the public {@link moveCell} has to do. Used by bulk range moves.
3017
+ */
3018
+ moveCellRC(fromRow, fromColumn, toRow, toColumn) {
3019
+ if (fromRow === toRow && fromColumn === toColumn) return;
3020
+ const fromRef = cellRefFromRowCol(fromRow, fromColumn);
3021
+ const toRef = cellRefFromRowCol(toRow, toColumn);
3022
+ const src = __privateMethod(this, _Worksheet_instances, takeCellDataAt_fn).call(this, fromRow, fromRef);
3023
+ if (!src) {
3024
+ __privateMethod(this, _Worksheet_instances, takeCellDataAt_fn).call(this, toRow, toRef);
3025
+ return;
3026
+ }
3027
+ const moved = { ...src, reference: toRef };
3028
+ if (moved.arrayRef) {
3029
+ moved.arrayRef = translateRangeRef(moved.arrayRef, toRow - fromRow, toColumn - fromColumn);
3030
+ }
3031
+ __privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, toRow).cells.set(toRef, moved);
3032
+ }
2153
3033
  /**
2154
3034
  * Returns the 1-based extent of populated cells (largest row and column that
2155
3035
  * contain data). Returns zeros for an empty sheet. Useful for snapshotting a
2156
- * sheet into a dense 2D array.
3036
+ * sheet into a dense 2D array — though {@link rows} and {@link cells} are
3037
+ * cheaper when a dense shape is not actually required.
2157
3038
  */
2158
3039
  get usedBounds() {
2159
3040
  let rowCount = 0;
@@ -2162,7 +3043,7 @@ var Worksheet = class {
2162
3043
  if (row.cells.size === 0) continue;
2163
3044
  if (row.rowIndex > rowCount) rowCount = row.rowIndex;
2164
3045
  for (const ref of row.cells.keys()) {
2165
- const col = parseCellRef(ref).column;
3046
+ const col = columnFromRef(ref, refSplit(ref));
2166
3047
  if (col > columnCount) columnCount = col;
2167
3048
  }
2168
3049
  }
@@ -2173,24 +3054,72 @@ var Worksheet = class {
2173
3054
  setColumnWidth(columnIndex, width) {
2174
3055
  if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
2175
3056
  if (width <= 0) throw new RangeError("Width must be > 0.");
2176
- __privateSet(this, _cols, __privateGet(this, _cols).filter((c) => !(c.min <= columnIndex && c.max >= columnIndex)));
2177
- __privateGet(this, _cols).push({ min: columnIndex, max: columnIndex, width, customWidth: true });
2178
- __privateGet(this, _cols).sort((a, b) => a.min - b.min);
3057
+ 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 });
3058
+ def.width = width;
3059
+ def.customWidth = true;
3060
+ }
3061
+ /**
3062
+ * Removes the explicit width of a column so it falls back to the sheet
3063
+ * default. Counterpart to {@link setColumnWidth}; afterwards
3064
+ * {@link getColumnWidth} returns `undefined`. A hidden column stays hidden.
3065
+ */
3066
+ resetColumnWidth(columnIndex) {
3067
+ if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
3068
+ const def = __privateMethod(this, _Worksheet_instances, splitColSpan_fn).call(this, columnIndex);
3069
+ if (!def) return;
3070
+ def.width = void 0;
3071
+ def.customWidth = false;
3072
+ __privateMethod(this, _Worksheet_instances, pruneColDefs_fn).call(this);
3073
+ }
3074
+ /**
3075
+ * Shows or hides a column (1-based index). Hidden columns round-trip to the
3076
+ * file as `<col hidden="1">` and keep any explicit width.
3077
+ */
3078
+ setColumnHidden(columnIndex, hidden = true) {
3079
+ if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
3080
+ const def = __privateMethod(this, _Worksheet_instances, splitColSpan_fn).call(this, columnIndex);
3081
+ if (def) {
3082
+ def.hidden = hidden;
3083
+ __privateMethod(this, _Worksheet_instances, pruneColDefs_fn).call(this);
3084
+ } else if (hidden) {
3085
+ __privateMethod(this, _Worksheet_instances, insertColDef_fn).call(this, { min: columnIndex, max: columnIndex, customWidth: false, hidden: true });
3086
+ }
3087
+ }
3088
+ /** True if the given 1-based column is hidden. */
3089
+ isColumnHidden(columnIndex) {
3090
+ if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
3091
+ return __privateMethod(this, _Worksheet_instances, findColDef_fn).call(this, columnIndex)?.hidden === true;
3092
+ }
3093
+ /**
3094
+ * Returns the explicit width of a column (1-based index), or `undefined` when
3095
+ * the column uses the sheet default. Counterpart to {@link setColumnWidth};
3096
+ * widths parsed from an existing file are visible here too, so column layout
3097
+ * can be round-tripped.
3098
+ */
3099
+ getColumnWidth(columnIndex) {
3100
+ if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
3101
+ return __privateMethod(this, _Worksheet_instances, findColDef_fn).call(this, columnIndex)?.width;
2179
3102
  }
2180
3103
  /** Auto-fits column width based on content (approximate). */
2181
3104
  autoFitColumn(columnIndex) {
2182
- if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
2183
- let maxLen = 8;
2184
- for (const row of __privateGet(this, _rows).values()) {
2185
- const ref = cellRefFromRowCol(row.rowIndex, columnIndex);
2186
- const cellData = row.cells.get(ref);
2187
- if (cellData) {
2188
- const cell = new Cell(cellData, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2));
2189
- const textLen = cell.getText().length;
2190
- if (textLen > maxLen) maxLen = textLen;
2191
- }
3105
+ this.autoFitColumns(columnIndex, columnIndex);
3106
+ }
3107
+ /**
3108
+ * Auto-fits an inclusive span of columns (1-based) in a single pass over the
3109
+ * populated cells, instead of re-scanning the sheet once per column.
3110
+ */
3111
+ autoFitColumns(firstColumn, lastColumn) {
3112
+ if (firstColumn < 1) throw new RangeError("Column index must be >= 1.");
3113
+ if (lastColumn < firstColumn) return;
3114
+ const maxLen = /* @__PURE__ */ new Map();
3115
+ this.forEachCellIn(1, firstColumn, EXCEL_MAX_ROWS, lastColumn, (cell, _row, column) => {
3116
+ const len = cell.getText().length;
3117
+ if (len > (maxLen.get(column) ?? 0)) maxLen.set(column, len);
3118
+ });
3119
+ for (let col = firstColumn; col <= lastColumn; col++) {
3120
+ const len = Math.max(maxLen.get(col) ?? 0, 8);
3121
+ this.setColumnWidth(col, Math.min(len * 1.2 + 2, 255));
2192
3122
  }
2193
- this.setColumnWidth(columnIndex, Math.min(maxLen * 1.2 + 2, 255));
2194
3123
  }
2195
3124
  /** Sets the height of a row (1-based index). */
2196
3125
  setRowHeight(rowIndex, height) {
@@ -2200,6 +3129,27 @@ var Worksheet = class {
2200
3129
  row.height = height;
2201
3130
  row.customHeight = true;
2202
3131
  }
3132
+ /**
3133
+ * Returns the explicit height of a row (1-based index), or `undefined` when
3134
+ * the row uses the sheet default. Counterpart to {@link setRowHeight}.
3135
+ */
3136
+ getRowHeight(rowIndex) {
3137
+ if (rowIndex < 1) throw new RangeError("Row index must be >= 1.");
3138
+ return __privateGet(this, _rows).get(rowIndex)?.height;
3139
+ }
3140
+ /**
3141
+ * Removes the explicit height of a row so it falls back to the sheet
3142
+ * default. Counterpart to {@link setRowHeight}; afterwards
3143
+ * {@link getRowHeight} returns `undefined`. A hidden row stays hidden.
3144
+ */
3145
+ resetRowHeight(rowIndex) {
3146
+ if (rowIndex < 1) throw new RangeError("Row index must be >= 1.");
3147
+ const row = __privateGet(this, _rows).get(rowIndex);
3148
+ if (!row) return;
3149
+ row.height = void 0;
3150
+ row.customHeight = void 0;
3151
+ if (row.cells.size === 0 && !row.hidden) __privateGet(this, _rows).delete(rowIndex);
3152
+ }
2203
3153
  /** Shows or hides a row (1-based index). Hidden rows affect SUBTOTAL(101-111). */
2204
3154
  setRowHidden(rowIndex, hidden = true) {
2205
3155
  if (rowIndex < 1) throw new RangeError("Row index must be >= 1.");
@@ -2209,6 +3159,137 @@ var Worksheet = class {
2209
3159
  isRowHidden(rowIndex) {
2210
3160
  return __privateGet(this, _rows).get(rowIndex)?.hidden === true;
2211
3161
  }
3162
+ /**
3163
+ * The 1-based indices of every hidden row, ascending. Sparse counterpart to
3164
+ * probing {@link isRowHidden} across {@link usedBounds}.
3165
+ */
3166
+ get hiddenRows() {
3167
+ const result = [];
3168
+ for (const row of __privateGet(this, _rows).values()) {
3169
+ if (row.hidden) result.push(row.rowIndex);
3170
+ }
3171
+ return result.sort((a, b) => a - b);
3172
+ }
3173
+ /**
3174
+ * The 1-based indices of every hidden column, ascending. Counterpart to
3175
+ * {@link isColumnHidden}, in the same spirit as {@link hiddenRows}.
3176
+ */
3177
+ get hiddenColumns() {
3178
+ const result = [];
3179
+ for (const def of __privateGet(this, _cols)) {
3180
+ if (!def.hidden) continue;
3181
+ for (let c = def.min; c <= def.max; c++) result.push(c);
3182
+ }
3183
+ return result.sort((a, b) => a - b);
3184
+ }
3185
+ // ── Structural editing ─────────────────────────────────────────────────────
3186
+ /**
3187
+ * Inserts `count` empty rows starting at row `at` (1-based). Everything at
3188
+ * or below `at` moves down: cell values, formulas (references are rewritten
3189
+ * the way Excel does), styles, row heights, hidden flags, merges,
3190
+ * autofilter and data-validation ranges.
3191
+ *
3192
+ * Frozen panes are a view property and stay put. Drawing/chart anchors
3193
+ * preserved from a loaded file are opaque XML and do NOT shift — images
3194
+ * keep their absolute position (known limitation).
3195
+ *
3196
+ * When formulas on OTHER sheets reference this sheet, use the
3197
+ * Workbook-level counterpart so they are rewritten too, or call
3198
+ * `applyExternalShift` on each sheet yourself. If a RecalcEngine is
3199
+ * attached, notify it afterwards (e.g. `engine.onRowsInserted(...)`).
3200
+ *
3201
+ * @example
3202
+ * ws.insertRows(3, 2); // pushes row 3 and below down by two rows
3203
+ */
3204
+ insertRows(at, count = 1) {
3205
+ return __privateMethod(this, _Worksheet_instances, applyStructural_fn).call(this, "row", "insert", at, count);
3206
+ }
3207
+ /**
3208
+ * Deletes `count` rows starting at row `at` (1-based). Rows below move up;
3209
+ * formulas referencing the deleted band get `#REF!` (fully inside) or
3210
+ * shrink (partially inside), exactly like Excel. See {@link insertRows}
3211
+ * for the shifting rules and known limitations.
3212
+ */
3213
+ deleteRows(at, count = 1) {
3214
+ return __privateMethod(this, _Worksheet_instances, applyStructural_fn).call(this, "row", "delete", at, count);
3215
+ }
3216
+ /**
3217
+ * Inserts `count` empty columns starting at column `at` (1-based).
3218
+ * Column-level counterpart to {@link insertRows}; widths and hidden flags
3219
+ * shift with the columns.
3220
+ */
3221
+ insertColumns(at, count = 1) {
3222
+ return __privateMethod(this, _Worksheet_instances, applyStructural_fn).call(this, "col", "insert", at, count);
3223
+ }
3224
+ /**
3225
+ * Deletes `count` columns starting at column `at` (1-based).
3226
+ * Column-level counterpart to {@link deleteRows}.
3227
+ */
3228
+ deleteColumns(at, count = 1) {
3229
+ return __privateMethod(this, _Worksheet_instances, applyStructural_fn).call(this, "col", "delete", at, count);
3230
+ }
3231
+ /**
3232
+ * @internal Rewrites formulas on THIS sheet after a structural edit on a
3233
+ * DIFFERENT sheet (named by `shift.sheetName`), so qualified references
3234
+ * like `'Data'!A5` stay correct. Cell positions here do not change.
3235
+ */
3236
+ applyExternalShift(shift) {
3237
+ return { refErrors: __privateMethod(this, _Worksheet_instances, rewriteFormulas_fn).call(this, shift) };
3238
+ }
3239
+ /**
3240
+ * Moves a cell's full contents — value, formula, style and spill anchor —
3241
+ * to another cell, overwriting the destination and clearing the source.
3242
+ * A missing source clears the destination. This is a literal move: formula
3243
+ * text is NOT adjusted for the new position.
3244
+ *
3245
+ * @example
3246
+ * ws.moveCell('A1', 'C5');
3247
+ */
3248
+ moveCell(from, to) {
3249
+ const fromRef = normalizeRef(from);
3250
+ const toRef = normalizeRef(to);
3251
+ if (fromRef === toRef) return;
3252
+ const src = __privateMethod(this, _Worksheet_instances, takeCellData_fn).call(this, fromRef);
3253
+ if (!src) {
3254
+ __privateMethod(this, _Worksheet_instances, deleteCellData_fn).call(this, toRef);
3255
+ return;
3256
+ }
3257
+ const fromPos = parseCellRef(fromRef);
3258
+ const toPos = parseCellRef(toRef);
3259
+ const moved = { ...src, reference: toRef };
3260
+ if (moved.arrayRef) {
3261
+ moved.arrayRef = translateRangeRef(
3262
+ moved.arrayRef,
3263
+ toPos.row - fromPos.row,
3264
+ toPos.column - fromPos.column
3265
+ );
3266
+ }
3267
+ __privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, toPos.row).cells.set(toRef, moved);
3268
+ }
3269
+ /**
3270
+ * Copies a cell's value, formula and style to another cell, overwriting the
3271
+ * destination and leaving the source untouched. A missing source clears the
3272
+ * destination. This is a literal copy: formula references are NOT adjusted,
3273
+ * and the spill anchor (`arrayRef`) is not copied — a recalc engine
3274
+ * re-establishes it.
3275
+ *
3276
+ * @example
3277
+ * ws.copyCell('A1', 'C5');
3278
+ */
3279
+ copyCell(from, to) {
3280
+ const fromRef = normalizeRef(from);
3281
+ const toRef = normalizeRef(to);
3282
+ if (fromRef === toRef) return;
3283
+ const srcRow = __privateGet(this, _rows).get(parseCellRef(fromRef).row);
3284
+ const src = srcRow?.cells.get(fromRef);
3285
+ if (!src) {
3286
+ __privateMethod(this, _Worksheet_instances, deleteCellData_fn).call(this, toRef);
3287
+ return;
3288
+ }
3289
+ const toPos = parseCellRef(toRef);
3290
+ const copied = { ...src, reference: toRef, arrayRef: void 0 };
3291
+ __privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, toPos.row).cells.set(toRef, copied);
3292
+ }
2212
3293
  /** Merges cells in the given range (e.g. "A1:B2"). */
2213
3294
  mergeCells(rangeReference) {
2214
3295
  if (!rangeReference) throw new Error("Range reference cannot be empty.");
@@ -2218,6 +3299,14 @@ var Worksheet = class {
2218
3299
  unmergeCells(rangeReference) {
2219
3300
  __privateSet(this, _mergeCells, __privateGet(this, _mergeCells).filter((m) => m.ref !== rangeReference));
2220
3301
  }
3302
+ /**
3303
+ * All merged ranges on this sheet, in the order they were added (or parsed).
3304
+ * Includes merges read from an existing file, so callers never need to track
3305
+ * their own copy of the list.
3306
+ */
3307
+ get merges() {
3308
+ return __privateGet(this, _mergeCells).map((m) => m.ref);
3309
+ }
2221
3310
  /**
2222
3311
  * Freezes rows and/or columns.
2223
3312
  * @param row Number of rows to freeze (0 = none)
@@ -2247,6 +3336,119 @@ var Worksheet = class {
2247
3336
  get visibility() {
2248
3337
  return __privateGet(this, _visibility);
2249
3338
  }
3339
+ // ── Sheet protection ───────────────────────────────────────────────────────
3340
+ /**
3341
+ * Turns on sheet protection, optionally relaxing individual permissions.
3342
+ * Cells are locked by default in Excel, so protecting a sheet makes all of
3343
+ * them read-only; clear a cell style's `locked` flag to leave a cell editable.
3344
+ *
3345
+ * Excel's sheet protection is a UI guard, **not** security: the contents stay
3346
+ * readable to anything that opens the file. This library therefore never
3347
+ * creates a protection password. A password read from an existing file is
3348
+ * preserved so the sheet round-trips unchanged.
3349
+ *
3350
+ * @example
3351
+ * ws.protect(); // lock everything
3352
+ * ws.protect({ allowSort: true, allowAutoFilter: true });
3353
+ */
3354
+ protect(options) {
3355
+ __privateSet(this, _protection, { ...PROTECTION_DEFAULTS, ...options });
3356
+ }
3357
+ /** Turns off sheet protection, discarding any password read from a file. */
3358
+ unprotect() {
3359
+ __privateSet(this, _protection, void 0);
3360
+ __privateSet(this, _protectionCredentials, "");
3361
+ }
3362
+ /** True when this sheet is protected. */
3363
+ get isProtected() {
3364
+ return __privateGet(this, _protection) !== void 0;
3365
+ }
3366
+ /** The active permissions, or `undefined` when the sheet is not protected. */
3367
+ get protection() {
3368
+ return __privateGet(this, _protection) ? { ...__privateGet(this, _protection) } : void 0;
3369
+ }
3370
+ // ── Hyperlinks ─────────────────────────────────────────────────────────────
3371
+ /**
3372
+ * Attaches a hyperlink to a cell or range. Give `target` for an external
3373
+ * destination or `location` for one inside the workbook; passing both keeps
3374
+ * the external target and uses the location as its fragment, which is how
3375
+ * Excel links to an anchor within a document.
3376
+ *
3377
+ * A cell's displayed text still comes from its own value — set that
3378
+ * separately; `display` only overrides what Excel shows in the edit bar.
3379
+ *
3380
+ * @example
3381
+ * ws.getCell('A1').setValue('Anthropic');
3382
+ * ws.setHyperlink('A1', { target: 'https://anthropic.com', tooltip: 'Open site' });
3383
+ * ws.setHyperlink('B1', { location: "'Sheet2'!A1" });
3384
+ */
3385
+ setHyperlink(ref, link) {
3386
+ if (!link.target && !link.location) {
3387
+ throw new Error("A hyperlink needs a target (external) or a location (in-workbook).");
3388
+ }
3389
+ const normalized = normalizeRangeRef(ref);
3390
+ __privateGet(this, _hyperlinks).set(normalized, { ref: normalized, ...link });
3391
+ }
3392
+ /** The hyperlink covering exactly this ref, or undefined. */
3393
+ getHyperlink(ref) {
3394
+ return __privateGet(this, _hyperlinks).get(normalizeRangeRef(ref));
3395
+ }
3396
+ /** Removes the hyperlink on this ref. Returns true if one was there. */
3397
+ removeHyperlink(ref) {
3398
+ return __privateGet(this, _hyperlinks).delete(normalizeRangeRef(ref));
3399
+ }
3400
+ /** Every hyperlink on the sheet, including ones read from a file. */
3401
+ get hyperlinks() {
3402
+ return [...__privateGet(this, _hyperlinks).values()];
3403
+ }
3404
+ // ── Comments (notes) ───────────────────────────────────────────────────────
3405
+ /**
3406
+ * Attaches a comment (a "note" in current Excel versions) to a cell,
3407
+ * replacing any comment already there.
3408
+ *
3409
+ * Note that writing comments makes this sheet regenerate its comment and
3410
+ * legacy-drawing parts on save. If the file was opened with other legacy
3411
+ * drawing content on the same sheet — form controls, for instance — that
3412
+ * content is not reproduced. Reading comments has no such effect.
3413
+ *
3414
+ * @example
3415
+ * ws.setComment('B4', 'Check this figure', { author: 'Bill' });
3416
+ */
3417
+ setComment(ref, text, options) {
3418
+ const normalized = normalizeRef(ref);
3419
+ __privateGet(this, _comments).set(normalized, {
3420
+ ref: normalized,
3421
+ text,
3422
+ author: options?.author ?? ""
3423
+ });
3424
+ __privateSet(this, _commentsDirty, true);
3425
+ }
3426
+ /** The comment on a cell, or undefined. */
3427
+ getComment(ref) {
3428
+ return __privateGet(this, _comments).get(normalizeRef(ref));
3429
+ }
3430
+ /** Removes the comment on a cell. Returns true if one was there. */
3431
+ removeComment(ref) {
3432
+ const removed = __privateGet(this, _comments).delete(normalizeRef(ref));
3433
+ if (removed) __privateSet(this, _commentsDirty, true);
3434
+ return removed;
3435
+ }
3436
+ /** Every comment on the sheet, including ones read from a file. */
3437
+ get comments() {
3438
+ return [...__privateGet(this, _comments).values()];
3439
+ }
3440
+ /** @internal True when this sheet must write its own comment parts. */
3441
+ get ownsCommentParts() {
3442
+ return __privateGet(this, _commentsDirty) && __privateGet(this, _comments).size > 0;
3443
+ }
3444
+ /**
3445
+ * @internal True when the sheet took over its comment parts and any preserved
3446
+ * originals must be dropped — including the case where every comment was
3447
+ * deleted and nothing replaces them.
3448
+ */
3449
+ get commentPartsReplaced() {
3450
+ return __privateGet(this, _commentsDirty);
3451
+ }
2250
3452
  // ── Drawings (charts, images …) ──────────────────────────────────────────────
2251
3453
  /**
2252
3454
  * Attaches a drawing provider (e.g. a chart) to this worksheet.
@@ -2256,6 +3458,18 @@ var Worksheet = class {
2256
3458
  addDrawingProvider(provider) {
2257
3459
  __privateGet(this, _drawings).push(provider);
2258
3460
  }
3461
+ /**
3462
+ * Detaches every drawing from this worksheet — both providers added in memory
3463
+ * and any drawing preserved from the file the workbook was opened from.
3464
+ *
3465
+ * Call before re-adding charts when exporting the same in-memory workbook
3466
+ * repeatedly, otherwise each export appends another copy; call on its own to
3467
+ * deliberately drop a chart that came from the source file.
3468
+ */
3469
+ clearDrawings() {
3470
+ __privateSet(this, _drawings, []);
3471
+ __privateSet(this, _preservedTail, __privateGet(this, _preservedTail).filter((t) => t.tag !== "drawing"));
3472
+ }
2259
3473
  /** True when this worksheet has at least one drawing to serialise. */
2260
3474
  get hasDrawings() {
2261
3475
  return __privateGet(this, _drawings).length > 0;
@@ -2264,21 +3478,116 @@ var Worksheet = class {
2264
3478
  get drawingProviders() {
2265
3479
  return __privateGet(this, _drawings);
2266
3480
  }
3481
+ // ── Round-trip preservation (internal) ───────────────────────────────────────
3482
+ /**
3483
+ * @internal Relationships carried over from the source file, minus any whose
3484
+ * leaf element is no longer emitted (a drawing replaced by live providers, or
3485
+ * cleared outright). The save pipeline writes these into the sheet's rels.
3486
+ */
3487
+ get preservedRelationships() {
3488
+ const liveIds = new Set(__privateMethod(this, _Worksheet_instances, activeTail_fn).call(this).map((t) => t.relId));
3489
+ return __privateGet(this, _preservedRels).filter((r) => {
3490
+ if (r.type === REL_TYPES.comments) {
3491
+ return !this.commentPartsReplaced && __privateGet(this, _comments).size > 0;
3492
+ }
3493
+ return liveIds.has(r.id);
3494
+ });
3495
+ }
3496
+ /**
3497
+ * @internal Package paths of relationships that were preserved at load time
3498
+ * but are no longer referenced — a drawing replaced by live providers, or one
3499
+ * cleared outright. The save pipeline prunes these parts so the output has no
3500
+ * orphans.
3501
+ */
3502
+ get droppedPreservedTargets() {
3503
+ const liveIds = new Set(__privateMethod(this, _Worksheet_instances, activeTail_fn).call(this).map((t) => t.relId));
3504
+ return __privateGet(this, _preservedRels).filter((r) => !liveIds.has(r.id) && r.resolved !== void 0).map((r) => r.resolved);
3505
+ }
3506
+ /**
3507
+ * @internal Relationship id for a drawing built from live providers. Chosen to
3508
+ * clear every preserved id so the two sets cannot collide.
3509
+ */
3510
+ get drawingRelId() {
3511
+ return this.generatedRels().drawing ?? `rId${__privateMethod(this, _Worksheet_instances, relIdBase_fn).call(this) + 1}`;
3512
+ }
3513
+ /**
3514
+ * @internal Relationship ids for the parts this sheet generates itself.
3515
+ *
3516
+ * Derived purely from the model so the worksheet XML and the sheet's rels part
3517
+ * — which are written by different code — cannot disagree about an id. Order is
3518
+ * fixed: drawing, then external hyperlinks in insertion order, then the
3519
+ * comments part and its legacy VML drawing.
3520
+ */
3521
+ generatedRels() {
3522
+ let next = __privateMethod(this, _Worksheet_instances, relIdBase_fn).call(this);
3523
+ const result = { hyperlinks: /* @__PURE__ */ new Map() };
3524
+ if (this.hasDrawings) result.drawing = `rId${++next}`;
3525
+ for (const link of __privateGet(this, _hyperlinks).values()) {
3526
+ if (link.target) result.hyperlinks.set(link.ref, `rId${++next}`);
3527
+ }
3528
+ if (this.ownsCommentParts) {
3529
+ result.comments = `rId${++next}`;
3530
+ result.vmlDrawing = `rId${++next}`;
3531
+ }
3532
+ return result;
3533
+ }
3534
+ /** @internal Captures round-trip state from the source package. */
3535
+ loadPreservedState(worksheetXml, relationships) {
3536
+ __privateSet(this, _preservedRels, relationships.filter((r) => r.type !== REL_TYPES.hyperlink));
3537
+ const known = new Set(__privateGet(this, _preservedRels).map((r) => r.id));
3538
+ __privateSet(this, _preservedTail, parseTailElements(worksheetXml).filter((t) => known.has(t.relId)));
3539
+ const byId = new Map(relationships.map((r) => [r.id, r]));
3540
+ for (const [ref, relId] of __privateGet(this, _pendingHyperlinkRels)) {
3541
+ const rel = byId.get(relId);
3542
+ const link = __privateGet(this, _hyperlinks).get(ref);
3543
+ if (!rel || !link) continue;
3544
+ __privateGet(this, _hyperlinks).set(ref, { ...link, target: rel.target });
3545
+ }
3546
+ __privateGet(this, _pendingHyperlinkRels).clear();
3547
+ for (const [ref, link] of [...__privateGet(this, _hyperlinks)]) {
3548
+ if (!link.target && !link.location) __privateGet(this, _hyperlinks).delete(ref);
3549
+ }
3550
+ }
3551
+ /**
3552
+ * @internal Package paths of the comment parts this sheet read at load time.
3553
+ * The save pipeline drops them from the preserved set once the sheet takes
3554
+ * ownership, so they are not written twice.
3555
+ */
3556
+ commentPartPaths() {
3557
+ const paths = [];
3558
+ for (const rel of __privateGet(this, _preservedRels)) {
3559
+ if (!rel.resolved) continue;
3560
+ if (rel.type === REL_TYPES.comments || rel.type === REL_TYPES.vmlDrawing) {
3561
+ paths.push(rel.resolved);
3562
+ }
3563
+ }
3564
+ return paths;
3565
+ }
3566
+ /**
3567
+ * @internal The relationship pointing at this sheet's comments part, if the
3568
+ * sheet was loaded from a file that had one.
3569
+ */
3570
+ commentsPartPath() {
3571
+ return __privateGet(this, _preservedRels).find((r) => r.type === REL_TYPES.comments)?.resolved;
3572
+ }
2267
3573
  // ── XML serialisation ──────────────────────────────────────────────────────
2268
3574
  /** Builds the xl/worksheets/sheetN.xml string. */
2269
3575
  buildXml() {
3576
+ const rels = this.generatedRels();
3577
+ const sheetViewXml = __privateMethod(this, _Worksheet_instances, buildSheetViewXml_fn).call(this);
2270
3578
  const colsXml = __privateMethod(this, _Worksheet_instances, buildColsXml_fn).call(this);
2271
3579
  const sheetDataXml = __privateMethod(this, _Worksheet_instances, buildSheetDataXml_fn).call(this);
2272
- const mergeXml = __privateMethod(this, _Worksheet_instances, buildMergeCellsXml_fn).call(this);
3580
+ const protectionXml = __privateMethod(this, _Worksheet_instances, buildSheetProtectionXml_fn).call(this);
2273
3581
  const filterXml = __privateGet(this, _autoFilter) ? `<autoFilter ref="${__privateGet(this, _autoFilter).ref}"/>` : "";
3582
+ const mergeXml = __privateMethod(this, _Worksheet_instances, buildMergeCellsXml_fn).call(this);
2274
3583
  const validXml = __privateMethod(this, _Worksheet_instances, buildDataValidationsXml_fn).call(this);
2275
- const sheetViewXml = __privateMethod(this, _Worksheet_instances, buildSheetViewXml_fn).call(this);
2276
- const drawingXml = this.hasDrawings ? `<drawing r:id="${WORKSHEET_DRAWING_REL_ID}"/>` : "";
3584
+ const linksXml = __privateMethod(this, _Worksheet_instances, buildHyperlinksXml_fn).call(this, rels.hyperlinks);
3585
+ const tailXml = __privateMethod(this, _Worksheet_instances, buildTailXml_fn).call(this, rels);
2277
3586
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2278
3587
  <worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
2279
3588
  xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
2280
3589
  <sheetViews>${sheetViewXml}</sheetViews>
2281
- ${colsXml}${sheetDataXml}${filterXml}${mergeXml}${validXml}${drawingXml}
3590
+ ${colsXml}${sheetDataXml}${protectionXml}${filterXml}${mergeXml}${validXml}${linksXml}${tailXml}
2282
3591
  </worksheet>`;
2283
3592
  }
2284
3593
  /** Parses xl/worksheets/sheetN.xml and loads cells into memory. */
@@ -2289,12 +3598,17 @@ ${colsXml}${sheetDataXml}${filterXml}${mergeXml}${validXml}${drawingXml}
2289
3598
  __privateSet(this, _pane, void 0);
2290
3599
  __privateSet(this, _autoFilter, void 0);
2291
3600
  __privateSet(this, _dataValidations, []);
3601
+ __privateSet(this, _protection, void 0);
3602
+ __privateSet(this, _protectionCredentials, "");
3603
+ __privateGet(this, _hyperlinks).clear();
3604
+ __privateGet(this, _pendingHyperlinkRels).clear();
2292
3605
  const root = parseXml(xml2);
2293
3606
  for (const colEl of getElementsByTagName(root, "col")) {
3607
+ const widthAttr = getAttr(colEl, "width");
2294
3608
  __privateGet(this, _cols).push({
2295
3609
  min: parseInt(getAttr(colEl, "min") ?? "1", 10),
2296
3610
  max: parseInt(getAttr(colEl, "max") ?? "1", 10),
2297
- width: parseFloat(getAttr(colEl, "width") ?? "8"),
3611
+ width: widthAttr !== void 0 ? parseFloat(widthAttr) : void 0,
2298
3612
  customWidth: getAttr(colEl, "customWidth") === "1",
2299
3613
  hidden: getAttr(colEl, "hidden") === "1"
2300
3614
  });
@@ -2346,6 +3660,75 @@ ${colsXml}${sheetDataXml}${filterXml}${mergeXml}${validXml}${drawingXml}
2346
3660
  const formula = f1Els[0]?.textContent ?? "";
2347
3661
  if (sqref && formula) __privateGet(this, _dataValidations).push({ sqref, formula });
2348
3662
  }
3663
+ const protEl = getElementsByTagName(root, "sheetProtection")[0];
3664
+ if (protEl && getAttr(protEl, "sheet") === "1") {
3665
+ const protection = { ...PROTECTION_DEFAULTS };
3666
+ for (const [key, attr] of PROTECTION_ATTRS) {
3667
+ const raw = getAttr(protEl, attr);
3668
+ if (raw !== void 0) protection[key] = raw !== "1";
3669
+ }
3670
+ __privateSet(this, _protection, protection);
3671
+ let credentials = "";
3672
+ for (const attr of ["password", "algorithmName", "hashValue", "saltValue", "spinCount"]) {
3673
+ const raw = getAttr(protEl, attr);
3674
+ if (raw !== void 0) credentials += ` ${attr}="${escXmlAttr(raw)}"`;
3675
+ }
3676
+ __privateSet(this, _protectionCredentials, credentials);
3677
+ }
3678
+ for (const hlEl of getElementsByTagName(root, "hyperlink")) {
3679
+ const ref = getAttr(hlEl, "ref");
3680
+ if (!ref) continue;
3681
+ const normalized = normalizeRangeRef(ref);
3682
+ __privateGet(this, _hyperlinks).set(normalized, {
3683
+ ref: normalized,
3684
+ location: getAttr(hlEl, "location"),
3685
+ display: getAttr(hlEl, "display"),
3686
+ tooltip: getAttr(hlEl, "tooltip")
3687
+ });
3688
+ const relId = getAttr(hlEl, "r:id") ?? getAttr(hlEl, "id");
3689
+ if (relId) __privateGet(this, _pendingHyperlinkRels).set(normalized, relId);
3690
+ }
3691
+ }
3692
+ /** @internal Parses an `xl/comments*.xml` part into this sheet's comments. */
3693
+ loadCommentsXml(xml2) {
3694
+ const root = parseXml(xml2);
3695
+ const authors = getElementsByTagName(root, "author").map((a) => a.textContent);
3696
+ for (const cEl of getElementsByTagName(root, "comment")) {
3697
+ const ref = getAttr(cEl, "ref");
3698
+ if (!ref) continue;
3699
+ const authorId = parseInt(getAttr(cEl, "authorId") ?? "0", 10);
3700
+ const text = getElementsByTagName(cEl, "t").map((t) => t.textContent).join("");
3701
+ const normalized = normalizeRef(ref);
3702
+ __privateGet(this, _comments).set(normalized, {
3703
+ ref: normalized,
3704
+ text,
3705
+ author: authors[authorId] ?? ""
3706
+ });
3707
+ }
3708
+ __privateSet(this, _commentsDirty, false);
3709
+ }
3710
+ /** @internal The comments part content, when this sheet owns it. */
3711
+ buildCommentsXml() {
3712
+ const authors = [...new Set([...__privateGet(this, _comments).values()].map((c) => c.author))];
3713
+ const authorsXml = authors.map((a) => `<author>${escXml2(a)}</author>`).join("");
3714
+ const listXml = [...__privateGet(this, _comments).values()].map(
3715
+ (c) => `<comment ref="${c.ref}" authorId="${authors.indexOf(c.author)}"><text><r><t xml:space="preserve">${escXml2(c.text)}</t></r></text></comment>`
3716
+ ).join("");
3717
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3718
+ <comments xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><authors>${authorsXml}</authors><commentList>${listXml}</commentList></comments>`;
3719
+ }
3720
+ /**
3721
+ * @internal The legacy VML drawing that positions the comment boxes. Excel
3722
+ * needs it to draw the note indicator and popup; the comments part alone
3723
+ * carries only the text.
3724
+ */
3725
+ buildCommentsVml() {
3726
+ const shapes = [...__privateGet(this, _comments).values()].map((c, idx) => {
3727
+ const { row, column } = parseCellRef(c.ref);
3728
+ const anchor = `${column}, 15, ${row - 1}, 10, ${column + 2}, 15, ${row + 3}, 4`;
3729
+ 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>`;
3730
+ }).join("");
3731
+ 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>`;
2349
3732
  }
2350
3733
  };
2351
3734
  _sharedStrings2 = new WeakMap();
@@ -2359,13 +3742,263 @@ _autoFilter = new WeakMap();
2359
3742
  _dataValidations = new WeakMap();
2360
3743
  _visibility = new WeakMap();
2361
3744
  _drawings = new WeakMap();
3745
+ _protection = new WeakMap();
3746
+ _protectionCredentials = new WeakMap();
3747
+ _hyperlinks = new WeakMap();
3748
+ _comments = new WeakMap();
3749
+ _commentsDirty = new WeakMap();
3750
+ _pendingHyperlinkRels = new WeakMap();
3751
+ _preservedTail = new WeakMap();
3752
+ _preservedRels = new WeakMap();
2362
3753
  _Worksheet_instances = new WeakSet();
3754
+ /** Removes and returns a cell's data record, pruning an emptied bare row. */
3755
+ takeCellData_fn = function(ref) {
3756
+ return __privateMethod(this, _Worksheet_instances, takeCellDataAt_fn).call(this, parseCellRef(ref).row, ref);
3757
+ };
3758
+ takeCellDataAt_fn = function(row, ref) {
3759
+ const rowData = __privateGet(this, _rows).get(row);
3760
+ const data = rowData?.cells.get(ref);
3761
+ if (rowData && data) {
3762
+ rowData.cells.delete(ref);
3763
+ if (rowData.cells.size === 0 && !rowData.hidden && rowData.height === void 0) {
3764
+ __privateGet(this, _rows).delete(row);
3765
+ }
3766
+ }
3767
+ return data;
3768
+ };
3769
+ deleteCellData_fn = function(ref) {
3770
+ __privateMethod(this, _Worksheet_instances, takeCellData_fn).call(this, ref);
3771
+ };
3772
+ applyStructural_fn = function(dim, kind, at, count) {
3773
+ const what = dim === "row" ? "Row" : "Column";
3774
+ if (!Number.isInteger(at) || at < 1) throw new RangeError(`${what} index must be an integer >= 1.`);
3775
+ if (!Number.isInteger(count) || count < 1) throw new RangeError("Count must be an integer >= 1.");
3776
+ const shift = { dim, kind, at, count, sheetName: __privateGet(this, _name) };
3777
+ if (kind === "insert") {
3778
+ const limit = dim === "row" ? EXCEL_MAX_ROWS : EXCEL_MAX_COLUMNS;
3779
+ const used = dim === "row" ? __privateMethod(this, _Worksheet_instances, maxPopulatedRow_fn).call(this) : this.usedBounds.columnCount;
3780
+ if (used >= at && used + count > limit) {
3781
+ throw new RangeError(`Insert would push populated cells past the sheet limit of ${limit}.`);
3782
+ }
3783
+ }
3784
+ __privateMethod(this, _Worksheet_instances, shiftRowsAndCells_fn).call(this, shift);
3785
+ const refErrors = __privateMethod(this, _Worksheet_instances, rewriteFormulas_fn).call(this, shift);
3786
+ __privateMethod(this, _Worksheet_instances, shiftMerges_fn).call(this, shift);
3787
+ __privateMethod(this, _Worksheet_instances, shiftAutoFilter_fn).call(this, shift);
3788
+ __privateMethod(this, _Worksheet_instances, shiftValidations_fn).call(this, shift);
3789
+ __privateMethod(this, _Worksheet_instances, shiftHyperlinks_fn).call(this, shift);
3790
+ __privateMethod(this, _Worksheet_instances, shiftComments_fn).call(this, shift);
3791
+ if (dim === "col") __privateMethod(this, _Worksheet_instances, shiftColDefs_fn).call(this, shift);
3792
+ return { refErrors };
3793
+ };
3794
+ /**
3795
+ * Re-keys the row map / per-row cell maps and shifts every cell's spill range.
3796
+ *
3797
+ * Rows and columns before the shift boundary keep their keys, their reference
3798
+ * strings and their identity, so an edit near the end of a sheet costs
3799
+ * proportionally little rather than rebuilding the whole model. Spill ranges
3800
+ * are the one thing that must be visited everywhere, since a range belonging
3801
+ * to an unmoved anchor can still span the boundary — but that pass is a bare
3802
+ * property test per cell.
3803
+ */
3804
+ shiftRowsAndCells_fn = function(shift) {
3805
+ if (shift.dim === "row") __privateMethod(this, _Worksheet_instances, shiftRows_fn).call(this, shift);
3806
+ else __privateMethod(this, _Worksheet_instances, shiftColumns_fn).call(this, shift);
3807
+ };
3808
+ shiftRows_fn = function(shift) {
3809
+ const moved = [];
3810
+ for (const row of __privateGet(this, _rows).values()) {
3811
+ if (row.rowIndex < shift.at) {
3812
+ __privateMethod(this, _Worksheet_instances, shiftSpillRanges_fn).call(this, row, shift);
3813
+ continue;
3814
+ }
3815
+ const shifted = shiftIndex(row.rowIndex, shift);
3816
+ __privateGet(this, _rows).delete(row.rowIndex);
3817
+ if (shifted === null) continue;
3818
+ row.rowIndex = shifted;
3819
+ moved.push(row);
3820
+ }
3821
+ for (const row of moved) {
3822
+ const suffix = String(row.rowIndex);
3823
+ const newCells = /* @__PURE__ */ new Map();
3824
+ for (const cd of row.cells.values()) {
3825
+ const ref = cd.reference.slice(0, refSplit(cd.reference)) + suffix;
3826
+ cd.reference = ref;
3827
+ if (cd.arrayRef) cd.arrayRef = shiftRangeRef(cd.arrayRef, shift) ?? void 0;
3828
+ newCells.set(ref, cd);
3829
+ }
3830
+ row.cells = newCells;
3831
+ __privateGet(this, _rows).set(row.rowIndex, row);
3832
+ }
3833
+ };
3834
+ shiftColumns_fn = function(shift) {
3835
+ for (const row of __privateGet(this, _rows).values()) {
3836
+ let affected;
3837
+ for (const cd of row.cells.values()) {
3838
+ const ref = cd.reference;
3839
+ if (columnFromRef(ref, refSplit(ref)) < shift.at) {
3840
+ __privateMethod(this, _Worksheet_instances, shiftSpillRange_fn).call(this, cd, shift);
3841
+ continue;
3842
+ }
3843
+ (affected ?? (affected = [])).push(cd);
3844
+ }
3845
+ if (!affected) continue;
3846
+ for (const cd of affected) row.cells.delete(cd.reference);
3847
+ for (const cd of affected) {
3848
+ const split = refSplit(cd.reference);
3849
+ const shifted = shiftIndex(columnFromRef(cd.reference, split), shift);
3850
+ if (shifted === null) continue;
3851
+ const ref = columnLetter(shifted) + cd.reference.slice(split);
3852
+ cd.reference = ref;
3853
+ __privateMethod(this, _Worksheet_instances, shiftSpillRange_fn).call(this, cd, shift);
3854
+ row.cells.set(ref, cd);
3855
+ }
3856
+ }
3857
+ };
3858
+ /** Largest 1-based row index holding a cell, or 0. O(rows), no ref parsing. */
3859
+ maxPopulatedRow_fn = function() {
3860
+ let max = 0;
3861
+ for (const row of __privateGet(this, _rows).values()) {
3862
+ if (row.cells.size > 0 && row.rowIndex > max) max = row.rowIndex;
3863
+ }
3864
+ return max;
3865
+ };
3866
+ shiftSpillRanges_fn = function(row, shift) {
3867
+ for (const cd of row.cells.values()) __privateMethod(this, _Worksheet_instances, shiftSpillRange_fn).call(this, cd, shift);
3868
+ };
3869
+ shiftSpillRange_fn = function(cd, shift) {
3870
+ if (cd.arrayRef) cd.arrayRef = shiftRangeRef(cd.arrayRef, shift) ?? void 0;
3871
+ };
3872
+ /** Rewrites every formula on the sheet; returns refs that now hold #REF!. */
3873
+ rewriteFormulas_fn = function(shift) {
3874
+ const refErrors = [];
3875
+ for (const row of __privateGet(this, _rows).values()) {
3876
+ for (const cd of row.cells.values()) {
3877
+ if (!cd.formula) continue;
3878
+ const result = shiftFormulaRefs(cd.formula, shift, __privateGet(this, _name));
3879
+ if (result.changed) cd.formula = result.text;
3880
+ if (result.hasRefError) refErrors.push(cd.reference);
3881
+ }
3882
+ }
3883
+ return refErrors;
3884
+ };
3885
+ shiftMerges_fn = function(shift) {
3886
+ __privateSet(this, _mergeCells, __privateGet(this, _mergeCells).flatMap((m) => {
3887
+ const ref = shiftRangeRef(m.ref, shift);
3888
+ if (!ref) return [];
3889
+ const [a, b] = ref.split(":");
3890
+ if (!b || a === b) return [];
3891
+ return [{ ref }];
3892
+ }));
3893
+ };
3894
+ shiftAutoFilter_fn = function(shift) {
3895
+ if (!__privateGet(this, _autoFilter)) return;
3896
+ const ref = shiftRangeRef(__privateGet(this, _autoFilter).ref, shift);
3897
+ __privateSet(this, _autoFilter, ref ? { ref } : void 0);
3898
+ };
3899
+ shiftValidations_fn = function(shift) {
3900
+ __privateSet(this, _dataValidations, __privateGet(this, _dataValidations).flatMap((dv) => {
3901
+ const sqref = shiftSqref(dv.sqref, shift);
3902
+ if (!sqref) return [];
3903
+ const formula = shiftFormulaRefs(dv.formula, shift, __privateGet(this, _name)).text;
3904
+ return [{ sqref, formula }];
3905
+ }));
3906
+ };
3907
+ shiftHyperlinks_fn = function(shift) {
3908
+ const moved = /* @__PURE__ */ new Map();
3909
+ for (const link of __privateGet(this, _hyperlinks).values()) {
3910
+ const ref = shiftRangeRef(link.ref, shift);
3911
+ if (!ref) continue;
3912
+ moved.set(ref, { ...link, ref });
3913
+ }
3914
+ __privateSet(this, _hyperlinks, moved);
3915
+ };
3916
+ shiftComments_fn = function(shift) {
3917
+ const moved = /* @__PURE__ */ new Map();
3918
+ let changed = false;
3919
+ for (const comment of __privateGet(this, _comments).values()) {
3920
+ const ref = shiftRangeRef(comment.ref, shift);
3921
+ if (!ref) {
3922
+ changed = true;
3923
+ continue;
3924
+ }
3925
+ if (ref !== comment.ref) changed = true;
3926
+ moved.set(ref, { ...comment, ref });
3927
+ }
3928
+ __privateSet(this, _comments, moved);
3929
+ if (changed) __privateSet(this, _commentsDirty, true);
3930
+ };
3931
+ shiftColDefs_fn = function(shift) {
3932
+ __privateSet(this, _cols, __privateGet(this, _cols).flatMap((c) => {
3933
+ const span = shiftSpan(c.min, c.max, shift);
3934
+ return span ? [{ ...c, min: span.start, max: span.end }] : [];
3935
+ }));
3936
+ };
3937
+ /** Highest numeric preserved relationship id, so generated ids clear them all. */
3938
+ relIdBase_fn = function() {
3939
+ let max = 0;
3940
+ for (const r of __privateGet(this, _preservedRels)) {
3941
+ const n = RID_PATTERN.exec(r.id);
3942
+ if (n) max = Math.max(max, parseInt(n[1], 10));
3943
+ }
3944
+ return max;
3945
+ };
3946
+ /**
3947
+ * Preserved leaf elements that will actually be emitted. A sheet may only have
3948
+ * one `<drawing>`, so live providers suppress a preserved one — every consumer
3949
+ * (serialisation, relationships, pruning) must agree on that, hence one helper.
3950
+ */
3951
+ activeTail_fn = function() {
3952
+ return __privateGet(this, _preservedTail).filter((t) => {
3953
+ if (t.tag === "drawing") return !this.hasDrawings;
3954
+ if (t.tag === "legacyDrawing") return !this.commentPartsReplaced;
3955
+ return true;
3956
+ });
3957
+ };
3958
+ /**
3959
+ * The relationship-bearing leaf elements, in schema order: `drawing`,
3960
+ * `legacyDrawing`, `legacyDrawingHF`, `picture`. A sheet may hold only one of
3961
+ * each, so generated content replaces the preserved element of the same tag —
3962
+ * live drawing providers replace a preserved `<drawing>`, and comments this
3963
+ * sheet now owns replace a preserved `<legacyDrawing>`.
3964
+ */
3965
+ buildTailXml_fn = function(rels) {
3966
+ const preserved = __privateMethod(this, _Worksheet_instances, activeTail_fn).call(this);
3967
+ const byTag = (tag) => preserved.filter((t) => t.tag === tag).map((t) => t.xml).join("");
3968
+ const drawing = rels.drawing ? `<drawing r:id="${rels.drawing}"/>` : byTag("drawing");
3969
+ const legacy = rels.vmlDrawing ? `<legacyDrawing r:id="${rels.vmlDrawing}"/>` : byTag("legacyDrawing");
3970
+ return drawing + legacy + byTag("legacyDrawingHF") + byTag("picture");
3971
+ };
3972
+ buildSheetProtectionXml_fn = function() {
3973
+ if (!__privateGet(this, _protection)) return "";
3974
+ let attrs = ' sheet="1"';
3975
+ for (const [key, attr] of PROTECTION_ATTRS) {
3976
+ const blocked = !__privateGet(this, _protection)[key];
3977
+ const defaultBlocked = !PROTECTION_DEFAULTS[key];
3978
+ if (blocked !== defaultBlocked) attrs += ` ${attr}="${blocked ? 1 : 0}"`;
3979
+ }
3980
+ return `<sheetProtection${__privateGet(this, _protectionCredentials)}${attrs}/>`;
3981
+ };
3982
+ buildHyperlinksXml_fn = function(relIds) {
3983
+ if (__privateGet(this, _hyperlinks).size === 0) return "";
3984
+ const inner = [...__privateGet(this, _hyperlinks).values()].map((link) => {
3985
+ const relId = relIds.get(link.ref);
3986
+ const parts = [`ref="${link.ref}"`];
3987
+ if (relId) parts.push(`r:id="${relId}"`);
3988
+ if (link.location) parts.push(`location="${escXmlAttr(link.location)}"`);
3989
+ if (link.display) parts.push(`display="${escXmlAttr(link.display)}"`);
3990
+ if (link.tooltip) parts.push(`tooltip="${escXmlAttr(link.tooltip)}"`);
3991
+ return `<hyperlink ${parts.join(" ")}/>`;
3992
+ }).join("");
3993
+ return `<hyperlinks>${inner}</hyperlinks>`;
3994
+ };
2363
3995
  // ── Private XML builders ───────────────────────────────────────────────────
2364
3996
  buildColsXml_fn = function() {
2365
3997
  if (__privateGet(this, _cols).length === 0) return "";
2366
3998
  const inner = __privateGet(this, _cols).map((c) => {
3999
+ const width = c.width !== void 0 ? ` width="${c.width.toFixed(2)}" customWidth="${c.customWidth ? 1 : 0}"` : "";
2367
4000
  const hidden = c.hidden ? ' hidden="1"' : "";
2368
- return `<col min="${c.min}" max="${c.max}" width="${c.width.toFixed(2)}" customWidth="${c.customWidth ? 1 : 0}"${hidden}/>`;
4001
+ return `<col min="${c.min}" max="${c.max}"${width}${hidden}/>`;
2369
4002
  }).join("");
2370
4003
  return `<cols>${inner}</cols>`;
2371
4004
  };
@@ -2375,12 +4008,8 @@ buildSheetDataXml_fn = function() {
2375
4008
  const rowsXml = sortedRows.map((row) => {
2376
4009
  const ht = row.height !== void 0 && row.customHeight ? ` ht="${row.height}" customHeight="1"` : "";
2377
4010
  const hidden = row.hidden ? ' hidden="1"' : "";
2378
- const sortedCells = [...row.cells.values()].sort((a, b) => {
2379
- const ra = parseCellRef(a.reference);
2380
- const rb = parseCellRef(b.reference);
2381
- return ra.column - rb.column;
2382
- });
2383
- const cellsXml = sortedCells.map((cd) => new Cell(cd, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2)).toXml()).filter(Boolean).join("");
4011
+ const sortedCells = [...row.cells.values()].map((cd) => ({ cd, col: columnFromRef(cd.reference, refSplit(cd.reference)) })).sort((a, b) => a.col - b.col);
4012
+ const cellsXml = sortedCells.map(({ cd }) => new Cell(cd, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2)).toXml()).filter(Boolean).join("");
2384
4013
  if (!cellsXml && !ht && !hidden) return "";
2385
4014
  return `<row r="${row.rowIndex}"${ht}${hidden}>${cellsXml}</row>`;
2386
4015
  }).filter(Boolean).join("");
@@ -2409,6 +4038,61 @@ buildSheetViewXml_fn = function() {
2409
4038
  const ySplit = row > 0 ? ` ySplit="${row}"` : "";
2410
4039
  return `<sheetView workbookViewId="0"><pane${xSplit}${ySplit} topLeftCell="${topLeft}" activePane="${activePane}" state="frozen"/></sheetView>`;
2411
4040
  };
4041
+ /**
4042
+ * Returns the single-column ColDef covering `columnIndex`, splitting a wider
4043
+ * span into up to three pieces so the other columns keep their width/hidden
4044
+ * state. Returns undefined when no definition covers the column.
4045
+ */
4046
+ splitColSpan_fn = function(columnIndex) {
4047
+ const idx = __privateMethod(this, _Worksheet_instances, findColDefIndex_fn).call(this, columnIndex);
4048
+ if (idx === -1) return void 0;
4049
+ const span = __privateGet(this, _cols)[idx];
4050
+ if (span.min === span.max) return span;
4051
+ const pieces = [];
4052
+ if (span.min < columnIndex) pieces.push({ ...span, max: columnIndex - 1 });
4053
+ const target = { ...span, min: columnIndex, max: columnIndex };
4054
+ pieces.push(target);
4055
+ if (span.max > columnIndex) pieces.push({ ...span, min: columnIndex + 1 });
4056
+ __privateGet(this, _cols).splice(idx, 1, ...pieces);
4057
+ return target;
4058
+ };
4059
+ /**
4060
+ * Inserts a definition keeping `#cols` sorted by `min`. Binary insertion,
4061
+ * because re-sorting the whole array per call makes setting widths for every
4062
+ * column of a wide sheet quadratic.
4063
+ */
4064
+ insertColDef_fn = function(def) {
4065
+ let lo = 0;
4066
+ let hi = __privateGet(this, _cols).length;
4067
+ while (lo < hi) {
4068
+ const mid = lo + hi >>> 1;
4069
+ if (__privateGet(this, _cols)[mid].min < def.min) lo = mid + 1;
4070
+ else hi = mid;
4071
+ }
4072
+ __privateGet(this, _cols).splice(lo, 0, def);
4073
+ return def;
4074
+ };
4075
+ /** Index of the definition covering `columnIndex`, or -1. Binary search. */
4076
+ findColDefIndex_fn = function(columnIndex) {
4077
+ let lo = 0;
4078
+ let hi = __privateGet(this, _cols).length - 1;
4079
+ while (lo <= hi) {
4080
+ const mid = lo + hi >>> 1;
4081
+ const c = __privateGet(this, _cols)[mid];
4082
+ if (columnIndex < c.min) hi = mid - 1;
4083
+ else if (columnIndex > c.max) lo = mid + 1;
4084
+ else return mid;
4085
+ }
4086
+ return -1;
4087
+ };
4088
+ findColDef_fn = function(columnIndex) {
4089
+ const idx = __privateMethod(this, _Worksheet_instances, findColDefIndex_fn).call(this, columnIndex);
4090
+ return idx === -1 ? void 0 : __privateGet(this, _cols)[idx];
4091
+ };
4092
+ /** Drops definitions that no longer carry any information. */
4093
+ pruneColDefs_fn = function() {
4094
+ __privateSet(this, _cols, __privateGet(this, _cols).filter((c) => c.width !== void 0 || c.customWidth || c.hidden));
4095
+ };
2412
4096
  getOrCreateRow_fn = function(rowIndex) {
2413
4097
  let row = __privateGet(this, _rows).get(rowIndex);
2414
4098
  if (!row) {
@@ -2417,9 +4101,64 @@ getOrCreateRow_fn = function(rowIndex) {
2417
4101
  }
2418
4102
  return row;
2419
4103
  };
4104
+ var RID_PATTERN = /^rId(\d+)$/;
2420
4105
  function escXml2(s) {
2421
4106
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2422
4107
  }
4108
+ function escXmlAttr(s) {
4109
+ return escXml2(s).replace(/"/g, "&quot;");
4110
+ }
4111
+ function normalizeRef(ref) {
4112
+ const normalized = normalizeRefFast(ref);
4113
+ parseCellRef(normalized);
4114
+ return normalized;
4115
+ }
4116
+ function normalizeRangeRef(ref) {
4117
+ const normalized = normalizeRefFast(ref);
4118
+ const colon = normalized.indexOf(":");
4119
+ if (colon === -1) {
4120
+ parseCellRef(normalized);
4121
+ return normalized;
4122
+ }
4123
+ parseCellRef(normalized.slice(0, colon));
4124
+ parseCellRef(normalized.slice(colon + 1));
4125
+ return normalized;
4126
+ }
4127
+ function normalizeRefFast(ref) {
4128
+ return ref.indexOf("$") === -1 ? upperFast(ref) : ref.replace(/\$/g, "").toUpperCase();
4129
+ }
4130
+ function upperFast(s) {
4131
+ for (let i = 0; i < s.length; i++) {
4132
+ const code = s.charCodeAt(i);
4133
+ if (code >= 97 && code <= 122) return s.toUpperCase();
4134
+ }
4135
+ return s;
4136
+ }
4137
+ function refSplit(ref) {
4138
+ let i = 0;
4139
+ while (i < ref.length) {
4140
+ const code = ref.charCodeAt(i);
4141
+ if (code < 65 || code > 90) break;
4142
+ i++;
4143
+ }
4144
+ return i;
4145
+ }
4146
+ function columnFromRef(ref, split) {
4147
+ let col = 0;
4148
+ for (let i = 0; i < split; i++) col = col * 26 + (ref.charCodeAt(i) - 64);
4149
+ return col;
4150
+ }
4151
+ function translateRangeRef(ref, deltaRow, deltaCol) {
4152
+ const parts = ref.split(":").map((p) => {
4153
+ const { row, column } = parseCellRef(p);
4154
+ const r = row + deltaRow;
4155
+ const c = column + deltaCol;
4156
+ if (r < 1 || c < 1 || r > EXCEL_MAX_ROWS || c > EXCEL_MAX_COLUMNS) return null;
4157
+ return cellRefFromRowCol(r, c);
4158
+ });
4159
+ if (parts.some((p) => p === null)) return void 0;
4160
+ return parts.join(":");
4161
+ }
2423
4162
 
2424
4163
  // src/model/defined-name.ts
2425
4164
  var _names, _DefinedNameManager_instances, indexOf_fn;
@@ -2484,7 +4223,7 @@ indexOf_fn = function(name, scope) {
2484
4223
  };
2485
4224
 
2486
4225
  // src/model/workbook.ts
2487
- var _sharedStrings3, _styles3, _sheets, _definedNames, _closed, _Workbook_instances, assertOpen_fn, buildZipEntries_fn;
4226
+ var _sharedStrings3, _styles3, _sheets, _definedNames, _closed, _preservedParts, _preservedContentTypes, _orphanedTargets, _Workbook_instances, structuralEdit_fn, assertOpen_fn, seedPreservedParts_fn, preservedContentTypeDeclarations_fn, buildZipEntries_fn;
2488
4227
  var _Workbook = class _Workbook {
2489
4228
  constructor(sharedStrings, styles) {
2490
4229
  __privateAdd(this, _Workbook_instances);
@@ -2493,6 +4232,11 @@ var _Workbook = class _Workbook {
2493
4232
  __privateAdd(this, _sheets, []);
2494
4233
  __privateAdd(this, _definedNames, new DefinedNameManager());
2495
4234
  __privateAdd(this, _closed, false);
4235
+ /** Parts carried over verbatim from the file this workbook was opened from. */
4236
+ __privateAdd(this, _preservedParts, /* @__PURE__ */ new Map());
4237
+ __privateAdd(this, _preservedContentTypes);
4238
+ /** Preserved targets whose referring worksheet has been removed. */
4239
+ __privateAdd(this, _orphanedTargets, []);
2496
4240
  __privateSet(this, _sharedStrings3, sharedStrings);
2497
4241
  __privateSet(this, _styles3, styles);
2498
4242
  }
@@ -2533,8 +4277,24 @@ var _Workbook = class _Workbook {
2533
4277
  const ws = new Worksheet(name, ss, sm);
2534
4278
  if (state) ws.setVisibility(state);
2535
4279
  ws.loadFromXml(wsXml);
4280
+ if (wsPath) {
4281
+ const dir = wsPath.slice(0, wsPath.lastIndexOf("/"));
4282
+ const file = wsPath.slice(wsPath.lastIndexOf("/") + 1);
4283
+ const wsRelsXml = readXmlEntry(entries, `${dir}/_rels/${file}.rels`);
4284
+ if (wsRelsXml) {
4285
+ ws.loadPreservedState(wsXml, parseRelationships(wsRelsXml, wsPath));
4286
+ const commentsPath = ws.commentsPartPath();
4287
+ if (commentsPath) {
4288
+ const commentsXml = readXmlEntry(entries, commentsPath);
4289
+ if (commentsXml) ws.loadCommentsXml(commentsXml);
4290
+ }
4291
+ }
4292
+ }
2536
4293
  __privateGet(wb, _sheets).push({ worksheet: ws, sheetId, relId });
2537
4294
  }
4295
+ __privateSet(wb, _preservedParts, collectPreservedParts(entries));
4296
+ const ctXml = readXmlEntry(entries, "[Content_Types].xml");
4297
+ if (ctXml) __privateSet(wb, _preservedContentTypes, parseContentTypes(ctXml));
2538
4298
  for (const dn of getElementsByTagName(wbRoot, "definedName")) {
2539
4299
  const name = getAttr(dn, "name");
2540
4300
  if (!name) continue;
@@ -2584,6 +4344,30 @@ var _Workbook = class _Workbook {
2584
4344
  __privateGet(this, _sheets).push({ worksheet: ws, sheetId, relId });
2585
4345
  return ws;
2586
4346
  }
4347
+ /**
4348
+ * Removes the worksheet with the given name, along with any defined names
4349
+ * scoped to it. Throws if the sheet does not exist or is the last remaining
4350
+ * one — a workbook must always keep at least one sheet.
4351
+ *
4352
+ * Sheet ids and relationship ids are re-derived from document order on save,
4353
+ * so the remaining sheets stay consistent after a removal.
4354
+ */
4355
+ removeWorksheet(name) {
4356
+ __privateMethod(this, _Workbook_instances, assertOpen_fn).call(this);
4357
+ const idx = __privateGet(this, _sheets).findIndex((e) => e.worksheet.name === name);
4358
+ if (idx < 0) throw new Error(`Worksheet "${name}" not found.`);
4359
+ if (__privateGet(this, _sheets).length === 1) {
4360
+ throw new Error("Cannot remove the last worksheet; a workbook must have at least one.");
4361
+ }
4362
+ const removed = __privateGet(this, _sheets)[idx].worksheet;
4363
+ for (const rel of removed.preservedRelationships) {
4364
+ if (rel.resolved) __privateGet(this, _orphanedTargets).push(rel.resolved);
4365
+ }
4366
+ __privateGet(this, _sheets).splice(idx, 1);
4367
+ for (const dn of [...__privateGet(this, _definedNames).all]) {
4368
+ if (dn.scope === name) __privateGet(this, _definedNames).remove(dn.name, dn.scope);
4369
+ }
4370
+ }
2587
4371
  /**
2588
4372
  * Returns a worksheet by name. Throws if not found.
2589
4373
  */
@@ -2592,6 +4376,32 @@ var _Workbook = class _Workbook {
2592
4376
  if (!entry) throw new Error(`Worksheet "${name}" not found.`);
2593
4377
  return entry.worksheet;
2594
4378
  }
4379
+ // ── Structural editing (workbook-wide) ───────────────────────────────────────
4380
+ /**
4381
+ * Inserts rows on one sheet and keeps the whole workbook consistent:
4382
+ * formulas on OTHER sheets that reference the edited sheet, and defined
4383
+ * names, are rewritten too. Prefer this over `Worksheet.insertRows` in a
4384
+ * multi-sheet workbook.
4385
+ *
4386
+ * @returns The edited sheet's result; `refErrors` additionally includes
4387
+ * sheet-qualified refs (e.g. `"Other!B2"`) for affected formula
4388
+ * cells on other sheets.
4389
+ */
4390
+ insertRows(sheetName, at, count = 1) {
4391
+ return __privateMethod(this, _Workbook_instances, structuralEdit_fn).call(this, sheetName, "row", "insert", at, count);
4392
+ }
4393
+ /** Workbook-wide counterpart to `Worksheet.deleteRows`. See {@link insertRows}. */
4394
+ deleteRows(sheetName, at, count = 1) {
4395
+ return __privateMethod(this, _Workbook_instances, structuralEdit_fn).call(this, sheetName, "row", "delete", at, count);
4396
+ }
4397
+ /** Workbook-wide counterpart to `Worksheet.insertColumns`. See {@link insertRows}. */
4398
+ insertColumns(sheetName, at, count = 1) {
4399
+ return __privateMethod(this, _Workbook_instances, structuralEdit_fn).call(this, sheetName, "col", "insert", at, count);
4400
+ }
4401
+ /** Workbook-wide counterpart to `Worksheet.deleteColumns`. See {@link insertRows}. */
4402
+ deleteColumns(sheetName, at, count = 1) {
4403
+ return __privateMethod(this, _Workbook_instances, structuralEdit_fn).call(this, sheetName, "col", "delete", at, count);
4404
+ }
2595
4405
  // ── Defined names (named ranges) ─────────────────────────────────────────────
2596
4406
  /**
2597
4407
  * Defines (or replaces) a named range.
@@ -2668,19 +4478,89 @@ _styles3 = new WeakMap();
2668
4478
  _sheets = new WeakMap();
2669
4479
  _definedNames = new WeakMap();
2670
4480
  _closed = new WeakMap();
4481
+ _preservedParts = new WeakMap();
4482
+ _preservedContentTypes = new WeakMap();
4483
+ _orphanedTargets = new WeakMap();
2671
4484
  _Workbook_instances = new WeakSet();
4485
+ structuralEdit_fn = function(sheetName, dim, kind, at, count) {
4486
+ __privateMethod(this, _Workbook_instances, assertOpen_fn).call(this);
4487
+ const target = this.getWorksheet(sheetName);
4488
+ const result = dim === "row" ? kind === "insert" ? target.insertRows(at, count) : target.deleteRows(at, count) : kind === "insert" ? target.insertColumns(at, count) : target.deleteColumns(at, count);
4489
+ const shift = { dim, kind, at, count, sheetName: target.name };
4490
+ const refErrors = [...result.refErrors];
4491
+ for (const ws of this.worksheets) {
4492
+ if (ws === target) continue;
4493
+ const external = ws.applyExternalShift(shift);
4494
+ for (const ref of external.refErrors) refErrors.push(`${ws.name}!${ref}`);
4495
+ }
4496
+ for (const dn of [...__privateGet(this, _definedNames).all]) {
4497
+ const rewritten = shiftFormulaRefs(dn.refersTo, shift, dn.scope ?? "");
4498
+ if (rewritten.changed) __privateGet(this, _definedNames).define(dn.name, rewritten.text, dn.scope);
4499
+ }
4500
+ return { refErrors };
4501
+ };
2672
4502
  // ── Private helpers ────────────────────────────────────────────────────────
2673
4503
  assertOpen_fn = function() {
2674
4504
  if (__privateGet(this, _closed)) throw new Error("Workbook has been closed.");
2675
4505
  };
4506
+ /**
4507
+ * Copies preserved source parts into the output, minus those that are now
4508
+ * orphaned. A part is dropped when the only relationship pointing at it has
4509
+ * gone (a drawing replaced by live providers, a sheet removed, an explicit
4510
+ * `clearDrawings()`) and nothing else still reaches it.
4511
+ *
4512
+ * @returns the set of paths written, so later steps can avoid colliding with
4513
+ * them when naming newly generated parts.
4514
+ */
4515
+ seedPreservedParts_fn = function(entries) {
4516
+ if (__privateGet(this, _preservedParts).size === 0) return /* @__PURE__ */ new Set();
4517
+ const retained = /* @__PURE__ */ new Set();
4518
+ for (const { worksheet } of __privateGet(this, _sheets)) {
4519
+ for (const rel of worksheet.preservedRelationships) {
4520
+ if (rel.resolved) {
4521
+ for (const p of collectSubtree(__privateGet(this, _preservedParts), rel.resolved)) retained.add(p);
4522
+ }
4523
+ }
4524
+ }
4525
+ const candidates = [
4526
+ ...__privateGet(this, _sheets).flatMap((e) => [...e.worksheet.droppedPreservedTargets]),
4527
+ ...__privateGet(this, _orphanedTargets)
4528
+ ];
4529
+ const dropped = /* @__PURE__ */ new Set();
4530
+ for (const target of candidates) {
4531
+ for (const p of collectSubtree(__privateGet(this, _preservedParts), target)) {
4532
+ if (!retained.has(p)) dropped.add(p);
4533
+ }
4534
+ }
4535
+ const written = /* @__PURE__ */ new Set();
4536
+ for (const [path, bytes] of __privateGet(this, _preservedParts)) {
4537
+ if (dropped.has(path)) continue;
4538
+ entries.set(path, bytes);
4539
+ written.add(path);
4540
+ }
4541
+ return written;
4542
+ };
4543
+ /**
4544
+ * Content-type declarations for preserved parts. `<Default>` entries are kept
4545
+ * wholesale — they are extension-scoped and are what types binary parts such
4546
+ * as images — while `<Override>` entries are filtered to parts that survived.
4547
+ */
4548
+ preservedContentTypeDeclarations_fn = function(preservedPaths) {
4549
+ const ct = __privateGet(this, _preservedContentTypes);
4550
+ if (!ct) return { defaults: [], overrides: [] };
4551
+ const defaults = [...ct.defaults].filter(([ext]) => ext !== "rels" && ext !== "xml").map(([extension, contentType]) => ({ extension, contentType }));
4552
+ const overrides = [...ct.overrides].filter(([partName]) => preservedPaths.has(partName.replace(/^\//, ""))).map(([partName, contentType]) => ({ partName, contentType }));
4553
+ return { defaults, overrides };
4554
+ };
2676
4555
  buildZipEntries_fn = function() {
2677
4556
  const entries = /* @__PURE__ */ new Map();
2678
4557
  const sheetCount = __privateGet(this, _sheets).length;
4558
+ const preservedPaths = __privateMethod(this, _Workbook_instances, seedPreservedParts_fn).call(this, entries);
2679
4559
  entries.set("_rels/.rels", ROOT_RELS);
2680
- const sheetMetas = __privateGet(this, _sheets).map((e) => ({
4560
+ const sheetMetas = __privateGet(this, _sheets).map((e, i) => ({
2681
4561
  name: e.worksheet.name,
2682
- sheetId: e.sheetId,
2683
- relationshipId: e.relId,
4562
+ sheetId: i + 1,
4563
+ relationshipId: `rId${i + 1}`,
2684
4564
  state: e.worksheet.visibility !== "visible" ? e.worksheet.visibility : void 0
2685
4565
  }));
2686
4566
  const definedNameMetas = __privateGet(this, _definedNames).all.map((dn) => {
@@ -2699,38 +4579,79 @@ buildZipEntries_fn = function() {
2699
4579
  entries.set(`xl/worksheets/sheet${i + 1}.xml`, __privateGet(this, _sheets)[i].worksheet.buildXml());
2700
4580
  }
2701
4581
  const extraOverrides = [];
4582
+ const extraDefaults = [];
2702
4583
  let drawingCounter = 0;
2703
4584
  let partCounter = 0;
4585
+ let commentsCounter = 0;
2704
4586
  for (let i = 0; i < __privateGet(this, _sheets).length; i++) {
2705
4587
  const ws = __privateGet(this, _sheets)[i].worksheet;
2706
- if (!ws.hasDrawings) continue;
2707
- drawingCounter++;
2708
- const drawingPath = `xl/drawings/drawing${drawingCounter}.xml`;
2709
- const anchors = [];
2710
- const drawingRels = [];
2711
- ws.drawingProviders.forEach((provider, idx) => {
2712
- const relId = `rId${idx + 1}`;
2713
- anchors.push(provider.buildAnchorXml(relId));
2714
- partCounter++;
2715
- const part = provider.buildPart(partCounter);
2716
- entries.set(part.path, part.content);
2717
- extraOverrides.push({ partName: `/${part.path}`, contentType: part.contentType });
2718
- const target = `../${part.path.slice("xl/".length)}`;
2719
- drawingRels.push(
2720
- ` <Relationship Id="${relId}" Type="${part.relType}" Target="${target}"/>`
2721
- );
2722
- });
2723
- entries.set(drawingPath, buildDrawingXml(anchors.join("")));
2724
- entries.set(`xl/drawings/_rels/drawing${drawingCounter}.xml.rels`, buildRelsXml(drawingRels));
2725
- extraOverrides.push({ partName: `/${drawingPath}`, contentType: CONTENT_TYPES.drawing });
2726
- entries.set(
2727
- `xl/worksheets/_rels/sheet${i + 1}.xml.rels`,
2728
- buildRelsXml([
2729
- ` <Relationship Id="${WORKSHEET_DRAWING_REL_ID}" Type="${REL_TYPES.drawing}" Target="../drawings/drawing${drawingCounter}.xml"/>`
2730
- ])
4588
+ const sheetRels = ws.preservedRelationships.map(
4589
+ (r) => ` <Relationship Id="${r.id}" Type="${r.type}" Target="${escapeXml(r.target)}"${r.external ? ' TargetMode="External"' : ""}/>`
2731
4590
  );
4591
+ const generated = ws.generatedRels();
4592
+ for (const link of ws.hyperlinks) {
4593
+ const relId = generated.hyperlinks.get(link.ref);
4594
+ if (!relId || !link.target) continue;
4595
+ sheetRels.push(
4596
+ ` <Relationship Id="${relId}" Type="${REL_TYPES.hyperlink}" Target="${escapeXml(link.target)}" TargetMode="External"/>`
4597
+ );
4598
+ }
4599
+ if (generated.comments && generated.vmlDrawing) {
4600
+ do {
4601
+ commentsCounter++;
4602
+ } while (preservedPaths.has(`xl/comments${commentsCounter}.xml`) || preservedPaths.has(`xl/drawings/vmlDrawing${commentsCounter}.vml`));
4603
+ const commentsPath = `xl/comments${commentsCounter}.xml`;
4604
+ const vmlPath = `xl/drawings/vmlDrawing${commentsCounter}.vml`;
4605
+ entries.set(commentsPath, ws.buildCommentsXml());
4606
+ entries.set(vmlPath, ws.buildCommentsVml());
4607
+ extraOverrides.push({ partName: `/${commentsPath}`, contentType: CONTENT_TYPES.comments });
4608
+ if (!extraDefaults.some((d) => d.extension === "vml")) {
4609
+ extraDefaults.push({ extension: "vml", contentType: CONTENT_TYPES.vmlDrawing });
4610
+ }
4611
+ sheetRels.push(
4612
+ ` <Relationship Id="${generated.comments}" Type="${REL_TYPES.comments}" Target="../comments${commentsCounter}.xml"/>`,
4613
+ ` <Relationship Id="${generated.vmlDrawing}" Type="${REL_TYPES.vmlDrawing}" Target="../drawings/vmlDrawing${commentsCounter}.vml"/>`
4614
+ );
4615
+ }
4616
+ if (ws.hasDrawings) {
4617
+ do {
4618
+ drawingCounter++;
4619
+ } while (preservedPaths.has(`xl/drawings/drawing${drawingCounter}.xml`));
4620
+ const drawingPath = `xl/drawings/drawing${drawingCounter}.xml`;
4621
+ const anchors = [];
4622
+ const drawingRels = [];
4623
+ ws.drawingProviders.forEach((provider, idx) => {
4624
+ const relId = `rId${idx + 1}`;
4625
+ anchors.push(provider.buildAnchorXml(relId));
4626
+ let part = provider.buildPart(++partCounter);
4627
+ while (preservedPaths.has(part.path)) part = provider.buildPart(++partCounter);
4628
+ entries.set(part.path, part.content);
4629
+ extraOverrides.push({ partName: `/${part.path}`, contentType: part.contentType });
4630
+ const target = `../${part.path.slice("xl/".length)}`;
4631
+ drawingRels.push(
4632
+ ` <Relationship Id="${relId}" Type="${part.relType}" Target="${target}"/>`
4633
+ );
4634
+ });
4635
+ entries.set(drawingPath, buildDrawingXml(anchors.join("")));
4636
+ entries.set(`xl/drawings/_rels/drawing${drawingCounter}.xml.rels`, buildRelsXml(drawingRels));
4637
+ extraOverrides.push({ partName: `/${drawingPath}`, contentType: CONTENT_TYPES.drawing });
4638
+ sheetRels.push(
4639
+ ` <Relationship Id="${ws.drawingRelId}" Type="${REL_TYPES.drawing}" Target="../drawings/drawing${drawingCounter}.xml"/>`
4640
+ );
4641
+ }
4642
+ if (sheetRels.length > 0) {
4643
+ entries.set(`xl/worksheets/_rels/sheet${i + 1}.xml.rels`, buildRelsXml(sheetRels));
4644
+ }
2732
4645
  }
2733
- entries.set("[Content_Types].xml", buildContentTypes(sheetCount, extraOverrides));
4646
+ const preservedCt = __privateMethod(this, _Workbook_instances, preservedContentTypeDeclarations_fn).call(this, preservedPaths);
4647
+ entries.set(
4648
+ "[Content_Types].xml",
4649
+ buildContentTypes(
4650
+ sheetCount,
4651
+ [...preservedCt.overrides, ...extraOverrides],
4652
+ [...preservedCt.defaults, ...extraDefaults]
4653
+ )
4654
+ );
2734
4655
  return entries;
2735
4656
  };
2736
4657
  var Workbook = _Workbook;
@@ -2749,6 +4670,6 @@ function resolveWorksheetPath(relsXml, relId) {
2749
4670
  return void 0;
2750
4671
  }
2751
4672
 
2752
- 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, fromOADate, getAttr, getElementsByTagName, isDateFormatCode, isDateFormatId, listEntries, parseCellRef, parseRangeRef, parseXml, readXmlEntry, requireXmlEntry, setBinaryEntry, setXmlEntry, toOADate, unzipXlsx, xml, zipXlsx };
4673
+ 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 };
2753
4674
  //# sourceMappingURL=index.js.map
2754
4675
  //# sourceMappingURL=index.js.map