@devmm/puredocs-excel 1.0.5 → 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.cjs CHANGED
@@ -50,10 +50,10 @@ function parseMinimal(xmlString) {
50
50
  }
51
51
  function parseAttributes(raw) {
52
52
  const attrs = {};
53
- const re = /(\S+?)\s*=\s*["']([^"']*)["']/g;
53
+ const re = /([^\s=/>]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
54
54
  let m;
55
55
  while ((m = re.exec(raw)) !== null) {
56
- attrs[m[1]] = m[2].replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
56
+ attrs[m[1]] = decodeXmlEntities(m[2] ?? m[3] ?? "");
57
57
  }
58
58
  return attrs;
59
59
  }
@@ -107,7 +107,7 @@ function parseMinimal(xmlString) {
107
107
  } else {
108
108
  const textStart = pos;
109
109
  skipUntil("<");
110
- const text = xmlString.slice(textStart, pos).replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
110
+ const text = decodeXmlEntities(xmlString.slice(textStart, pos));
111
111
  if (stack.length > 0 && text.trim()) {
112
112
  stack[stack.length - 1].textContent += text;
113
113
  }
@@ -116,6 +116,10 @@ function parseMinimal(xmlString) {
116
116
  if (!root) throw new Error("XML parse error: empty document.");
117
117
  return root;
118
118
  }
119
+ function decodeXmlEntities(value) {
120
+ if (value.indexOf("&") === -1) return value;
121
+ return value.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
122
+ }
119
123
  function parseXml(xmlString) {
120
124
  if (typeof DOMParser !== "undefined") {
121
125
  return parseDom(xmlString);
@@ -296,7 +300,9 @@ var REL_TYPES = {
296
300
  styles: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
297
301
  chart: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart",
298
302
  drawing: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
299
- hyperlink: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
303
+ hyperlink: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
304
+ comments: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments",
305
+ vmlDrawing: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing"
300
306
  };
301
307
  var CONTENT_TYPES = {
302
308
  workbook: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
@@ -306,7 +312,9 @@ var CONTENT_TYPES = {
306
312
  drawing: "application/vnd.openxmlformats-officedocument.drawing+xml",
307
313
  chart: "application/vnd.openxmlformats-officedocument.drawingml.chart+xml",
308
314
  relationships: "application/vnd.openxmlformats-package.relationships+xml",
309
- coreProperties: "application/vnd.openxmlformats-package.core-properties+xml"
315
+ coreProperties: "application/vnd.openxmlformats-package.core-properties+xml",
316
+ comments: "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml",
317
+ vmlDrawing: "application/vnd.openxmlformats-officedocument.vmlDrawing"
310
318
  };
311
319
 
312
320
  // src/io/ooxml-templates.ts
@@ -451,8 +459,9 @@ function parseRelationships(relsXml, ownerPath) {
451
459
  const tag = m[0];
452
460
  const id = /\bId="([^"]*)"/.exec(tag)?.[1];
453
461
  const type = /\bType="([^"]*)"/.exec(tag)?.[1];
454
- const target = /\bTarget="([^"]*)"/.exec(tag)?.[1];
455
- if (!id || !type || target === void 0) continue;
462
+ const rawTarget = /\bTarget="([^"]*)"/.exec(tag)?.[1];
463
+ if (!id || !type || rawTarget === void 0) continue;
464
+ const target = decodeXmlEntities(rawTarget);
456
465
  const external = /TargetMode="External"/.test(tag);
457
466
  rels.push({
458
467
  id,
@@ -1671,10 +1680,15 @@ function splitSections(code) {
1671
1680
  sections.push(current);
1672
1681
  return sections;
1673
1682
  }
1683
+ function hasDateTokens(section) {
1684
+ const bare = section.replace(/"[^"]*"/g, "").replace(/\\./g, "").replace(/\[[^\]]*\]/g, "");
1685
+ return /[ymdhsYMDHS]/.test(bare);
1686
+ }
1674
1687
  function isUnsupported(section) {
1675
1688
  if (/\[[<>=]/.test(section)) return true;
1676
1689
  if (/[?]/.test(section)) return true;
1677
1690
  if (/[Ee][+-]/.test(section)) return true;
1691
+ if (hasDateTokens(section)) return true;
1678
1692
  return false;
1679
1693
  }
1680
1694
  function parseSection(section) {
@@ -1768,12 +1782,177 @@ function formatNumberWithCode(value, formatCode) {
1768
1782
  return renderNumber(magnitude, parseSection(section));
1769
1783
  }
1770
1784
 
1785
+ // src/style/date-format-renderer.ts
1786
+ var MONTHS = [
1787
+ "January",
1788
+ "February",
1789
+ "March",
1790
+ "April",
1791
+ "May",
1792
+ "June",
1793
+ "July",
1794
+ "August",
1795
+ "September",
1796
+ "October",
1797
+ "November",
1798
+ "December"
1799
+ ];
1800
+ var DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
1801
+ function firstSection(code) {
1802
+ let inQuote = false;
1803
+ for (let i = 0; i < code.length; i++) {
1804
+ const ch = code[i];
1805
+ if (ch === '"') inQuote = !inQuote;
1806
+ else if (ch === ";" && !inQuote) return code.slice(0, i);
1807
+ }
1808
+ return code;
1809
+ }
1810
+ function tokenize(section) {
1811
+ const tokens = [];
1812
+ let i = 0;
1813
+ const n = section.length;
1814
+ while (i < n) {
1815
+ const ch = section[i];
1816
+ if (ch === '"') {
1817
+ let j = i + 1;
1818
+ let text = "";
1819
+ while (j < n && section[j] !== '"') text += section[j++];
1820
+ tokens.push({ kind: "literal", text });
1821
+ i = j + 1;
1822
+ continue;
1823
+ }
1824
+ if (ch === "\\") {
1825
+ tokens.push({ kind: "literal", text: section[i + 1] ?? "" });
1826
+ i += 2;
1827
+ continue;
1828
+ }
1829
+ if (ch === "[") {
1830
+ const close = section.indexOf("]", i);
1831
+ if (close === -1) return null;
1832
+ const inner = section.slice(i + 1, close);
1833
+ if (/^[hmsHMS]+$/.test(inner)) return null;
1834
+ i = close + 1;
1835
+ continue;
1836
+ }
1837
+ const rest = section.slice(i);
1838
+ const ampm = /^(AM\/PM|A\/P|am\/pm|a\/p)/.exec(rest);
1839
+ if (ampm) {
1840
+ tokens.push({ kind: "ampm", text: ampm[1] });
1841
+ i += ampm[1].length;
1842
+ continue;
1843
+ }
1844
+ const lower = ch.toLowerCase();
1845
+ if ("ymdhs".includes(lower)) {
1846
+ let j = i;
1847
+ while (j < n && section[j].toLowerCase() === lower) j++;
1848
+ tokens.push({ kind: lower, text: section.slice(i, j) });
1849
+ i = j;
1850
+ if (lower === "s" && section[i] === "." && /\d|0/.test(section[i + 1] ?? "")) return null;
1851
+ continue;
1852
+ }
1853
+ if (/[a-zA-Z]/.test(ch)) return null;
1854
+ tokens.push({ kind: "literal", text: ch });
1855
+ i++;
1856
+ }
1857
+ return tokens;
1858
+ }
1859
+ function resolveMonthVsMinute(tokens) {
1860
+ const dateKinds = /* @__PURE__ */ new Set(["y", "m", "d", "h", "s"]);
1861
+ for (let t = 0; t < tokens.length; t++) {
1862
+ const token = tokens[t];
1863
+ if (token.kind !== "m" || token.text.length > 2) continue;
1864
+ let prev;
1865
+ for (let p = t - 1; p >= 0; p--) {
1866
+ if (dateKinds.has(tokens[p].kind)) {
1867
+ prev = tokens[p];
1868
+ break;
1869
+ }
1870
+ }
1871
+ let next;
1872
+ for (let q = t + 1; q < tokens.length; q++) {
1873
+ if (dateKinds.has(tokens[q].kind)) {
1874
+ next = tokens[q];
1875
+ break;
1876
+ }
1877
+ }
1878
+ if (prev?.kind === "h" || next?.kind === "s") token.kind = "minute";
1879
+ }
1880
+ }
1881
+ var pad = (v, len) => String(v).padStart(len, "0");
1882
+ function formatDateCode(date, formatCode) {
1883
+ if (!formatCode || formatCode === "General") return null;
1884
+ const tokens = tokenize(firstSection(formatCode));
1885
+ if (!tokens) return null;
1886
+ if (!tokens.some((t) => "ymdhs".includes(t.kind))) return null;
1887
+ resolveMonthVsMinute(tokens);
1888
+ const twelveHour = tokens.some((t) => t.kind === "ampm");
1889
+ const hours24 = date.getHours();
1890
+ const hours12 = hours24 % 12 === 0 ? 12 : hours24 % 12;
1891
+ let out = "";
1892
+ for (const t of tokens) {
1893
+ switch (t.kind) {
1894
+ case "literal":
1895
+ out += t.text;
1896
+ break;
1897
+ case "y":
1898
+ out += t.text.length <= 2 ? pad(date.getFullYear() % 100, 2) : String(date.getFullYear());
1899
+ break;
1900
+ case "m": {
1901
+ const month = date.getMonth();
1902
+ if (t.text.length === 1) out += String(month + 1);
1903
+ else if (t.text.length === 2) out += pad(month + 1, 2);
1904
+ else if (t.text.length === 3) out += MONTHS[month].slice(0, 3);
1905
+ else if (t.text.length === 5) out += MONTHS[month][0];
1906
+ else out += MONTHS[month];
1907
+ break;
1908
+ }
1909
+ case "minute":
1910
+ out += t.text.length === 1 ? String(date.getMinutes()) : pad(date.getMinutes(), 2);
1911
+ break;
1912
+ case "d": {
1913
+ const day = date.getDate();
1914
+ if (t.text.length === 1) out += String(day);
1915
+ else if (t.text.length === 2) out += pad(day, 2);
1916
+ else if (t.text.length === 3) out += DAYS[date.getDay()].slice(0, 3);
1917
+ else out += DAYS[date.getDay()];
1918
+ break;
1919
+ }
1920
+ case "h": {
1921
+ const h = twelveHour ? hours12 : hours24;
1922
+ out += t.text.length === 1 ? String(h) : pad(h, 2);
1923
+ break;
1924
+ }
1925
+ case "s":
1926
+ out += t.text.length === 1 ? String(date.getSeconds()) : pad(date.getSeconds(), 2);
1927
+ break;
1928
+ case "ampm": {
1929
+ const isAm = hours24 < 12;
1930
+ if (t.text === "a/p") out += isAm ? "a" : "p";
1931
+ else if (t.text === "A/P") out += isAm ? "A" : "P";
1932
+ else if (t.text === "am/pm") out += isAm ? "am" : "pm";
1933
+ else out += isAm ? "AM" : "PM";
1934
+ break;
1935
+ }
1936
+ }
1937
+ }
1938
+ return out;
1939
+ }
1940
+
1771
1941
  // src/model/cell-reference.ts
1942
+ var EXCEL_MAX_ROWS = 1048576;
1943
+ var EXCEL_MAX_COLUMNS = 16384;
1944
+ function needsNormalizing(ref) {
1945
+ for (let i = 0; i < ref.length; i++) {
1946
+ const code = ref.charCodeAt(i);
1947
+ if (code === 36 || code >= 97 && code <= 122) return true;
1948
+ }
1949
+ return false;
1950
+ }
1772
1951
  function parseCellRef(cellReference) {
1773
1952
  if (!cellReference) {
1774
1953
  throw new Error("Cell reference cannot be null or empty.");
1775
1954
  }
1776
- const ref = cellReference.replace(/\$/g, "").toUpperCase();
1955
+ const ref = needsNormalizing(cellReference) ? cellReference.replace(/\$/g, "").toUpperCase() : cellReference;
1777
1956
  let i = 0;
1778
1957
  let column = 0;
1779
1958
  while (i < ref.length && ref.charCodeAt(i) >= 65 && ref.charCodeAt(i) <= 90) {
@@ -1819,6 +1998,11 @@ function columnNumber(letters) {
1819
1998
  }
1820
1999
  return col;
1821
2000
  }
2001
+ function formatRangeRef(startRow, startColumn, endRow, endColumn) {
2002
+ const start = cellRefFromRowCol(startRow, startColumn);
2003
+ if (startRow === endRow && startColumn === endColumn) return start;
2004
+ return `${start}:${cellRefFromRowCol(endRow, endColumn)}`;
2005
+ }
1822
2006
  function parseRangeRef(rangeReference) {
1823
2007
  const parts = rangeReference.split(":");
1824
2008
  if (parts.length !== 2) {
@@ -1979,18 +2163,20 @@ var Cell = class {
1979
2163
  }
1980
2164
  /**
1981
2165
  * Returns the display text with the cell's number format applied, e.g. a
1982
- * value of 4.5 under `$#,##0.00` renders as `$4.50`.
2166
+ * value of 4.5 under `$#,##0.00` renders as `$4.50`, and a date under
2167
+ * `dd/mm/yyyy` as `31/01/2025`.
1983
2168
  *
1984
- * Falls back to {@link getText} for non-numeric values and for format codes
1985
- * outside the supported subset (fractions, scientific notation, conditional
1986
- * sections) — so the result is never a misleading rendering.
2169
+ * Falls back to {@link getText} for values with no format and for format
2170
+ * codes outside the supported subset (fractions, scientific notation,
2171
+ * conditional sections, elapsed time) — so the result is never a misleading
2172
+ * rendering.
1987
2173
  */
1988
2174
  getFormattedText() {
1989
2175
  const val = this.getValue();
1990
- if (typeof val !== "number") return this.getText();
2176
+ if (typeof val !== "number" && !(val instanceof Date)) return this.getText();
1991
2177
  const format = __privateGet(this, _styles).getCellStyle(__privateGet(this, _data).styleIndex).numberFormat;
1992
2178
  if (!format) return this.getText();
1993
- return formatNumberWithCode(val, format.formatCode) ?? this.getText();
2179
+ return val instanceof Date ? formatDateCode(val, format.formatCode) ?? this.getText() : formatNumberWithCode(val, format.formatCode) ?? this.getText();
1994
2180
  }
1995
2181
  // ── Formula API ────────────────────────────────────────────────────────────
1996
2182
  /**
@@ -2175,8 +2361,269 @@ function escXml(s) {
2175
2361
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2176
2362
  }
2177
2363
 
2364
+ // src/model/ref-shift.ts
2365
+ function shiftIndex(index, shift) {
2366
+ if (shift.kind === "insert") {
2367
+ return index >= shift.at ? index + shift.count : index;
2368
+ }
2369
+ const bandEnd = shift.at + shift.count - 1;
2370
+ if (index < shift.at) return index;
2371
+ if (index <= bandEnd) return null;
2372
+ return index - shift.count;
2373
+ }
2374
+ function shiftSpan(start, end, shift) {
2375
+ if (shift.kind === "insert") {
2376
+ return {
2377
+ start: start >= shift.at ? start + shift.count : start,
2378
+ end: end >= shift.at ? end + shift.count : end
2379
+ };
2380
+ }
2381
+ const bandEnd = shift.at + shift.count - 1;
2382
+ const newStart = start < shift.at ? start : start <= bandEnd ? shift.at : start - shift.count;
2383
+ const newEnd = end < shift.at ? end : end <= bandEnd ? shift.at - 1 : end - shift.count;
2384
+ return newEnd < newStart ? null : { start: newStart, end: newEnd };
2385
+ }
2386
+ function shiftRangeRef(ref, shift) {
2387
+ const [startRef, endRef] = ref.split(":");
2388
+ const start = parseCellRef(startRef);
2389
+ const end = endRef !== void 0 ? parseCellRef(endRef) : start;
2390
+ const rows = shift.dim === "row" ? shiftSpan(start.row, end.row, shift) : { start: start.row, end: end.row };
2391
+ const cols = shift.dim === "col" ? shiftSpan(start.column, end.column, shift) : { start: start.column, end: end.column };
2392
+ if (!rows || !cols) return null;
2393
+ const startOut = cellRefFromRowCol(rows.start, cols.start);
2394
+ if (endRef === void 0) return startOut;
2395
+ return `${startOut}:${cellRefFromRowCol(rows.end, cols.end)}`;
2396
+ }
2397
+ function shiftSqref(sqref, shift) {
2398
+ const surviving = sqref.split(/\s+/).filter(Boolean).map((part) => shiftRangeRef(part, shift)).filter((part) => part !== null);
2399
+ return surviving.length > 0 ? surviving.join(" ") : null;
2400
+ }
2401
+
2402
+ // src/model/formula-ref-shift.ts
2403
+ var MAX_ROW = EXCEL_MAX_ROWS;
2404
+ var MAX_COL = EXCEL_MAX_COLUMNS;
2405
+ function isWordChar(code) {
2406
+ return code >= 65 && code <= 90 || // A-Z
2407
+ code >= 97 && code <= 122 || // a-z
2408
+ code >= 48 && code <= 57 || // 0-9
2409
+ code === 95 || // _
2410
+ code === 46 || // .
2411
+ code === 36;
2412
+ }
2413
+ function parseCellComponent(word) {
2414
+ const m = /^(\$?)([A-Za-z]{1,3})(\$?)(\d+)$/.exec(word);
2415
+ if (!m) return null;
2416
+ const col = columnNumber(m[2]);
2417
+ const row = parseInt(m[4], 10);
2418
+ if (col > MAX_COL || row < 1 || row > MAX_ROW) return null;
2419
+ return { colAbs: m[1] === "$", col, rowAbs: m[3] === "$", row };
2420
+ }
2421
+ function parseColComponent(word) {
2422
+ const m = /^(\$?)([A-Za-z]{1,3})$/.exec(word);
2423
+ if (!m) return null;
2424
+ const col = columnNumber(m[2]);
2425
+ if (col > MAX_COL) return null;
2426
+ return { abs: m[1] === "$", col };
2427
+ }
2428
+ function parseRowComponent(word) {
2429
+ const m = /^(\$?)(\d+)$/.exec(word);
2430
+ if (!m) return null;
2431
+ const row = parseInt(m[2], 10);
2432
+ if (row < 1 || row > MAX_ROW) return null;
2433
+ return { abs: m[1] === "$", row };
2434
+ }
2435
+ function emitCell(c, row, col) {
2436
+ return `${c.colAbs ? "$" : ""}${columnLetter(col)}${c.rowAbs ? "$" : ""}${row}`;
2437
+ }
2438
+ function shiftFormulaRefs(formula, shift, formulaSheet) {
2439
+ const n = formula.length;
2440
+ const editedSheet = shift.sheetName.toUpperCase();
2441
+ const unqualifiedRewritable = formulaSheet.toUpperCase() === editedSheet;
2442
+ const isRowShift = shift.dim === "row";
2443
+ let out = "";
2444
+ let flushed = 0;
2445
+ let changed = false;
2446
+ let hasRefError = false;
2447
+ let i = 0;
2448
+ const replace = (start, text) => {
2449
+ if (text.length === i - start && formula.startsWith(text, start)) return;
2450
+ out += formula.slice(flushed, start) + text;
2451
+ flushed = i;
2452
+ changed = true;
2453
+ };
2454
+ const readWord = () => {
2455
+ let j = i;
2456
+ while (j < n && isWordChar(formula.charCodeAt(j))) j++;
2457
+ const w = formula.slice(i, j);
2458
+ i = j;
2459
+ return w;
2460
+ };
2461
+ const readQuoted = () => {
2462
+ let j = i + 1;
2463
+ while (j < n) {
2464
+ if (formula[j] === "'") {
2465
+ if (formula[j + 1] === "'") {
2466
+ j += 2;
2467
+ continue;
2468
+ }
2469
+ j++;
2470
+ break;
2471
+ }
2472
+ j++;
2473
+ }
2474
+ const q = formula.slice(i, j);
2475
+ i = j;
2476
+ return q;
2477
+ };
2478
+ const consumeRef = (refStart, rewritable) => {
2479
+ const first = readWord();
2480
+ let firstCell = parseCellComponent(first);
2481
+ let firstCol = firstCell ? null : parseColComponent(first);
2482
+ let firstRow = firstCell || firstCol ? null : parseRowComponent(first);
2483
+ let second = null;
2484
+ let secondCell = null;
2485
+ let secondCol = null;
2486
+ let secondRow = null;
2487
+ if (formula[i] === ":" && i + 1 < n && isWordChar(formula.charCodeAt(i + 1))) {
2488
+ const save = i;
2489
+ i++;
2490
+ const w = readWord();
2491
+ if (firstCell) secondCell = parseCellComponent(w);
2492
+ else if (firstCol) secondCol = parseColComponent(w);
2493
+ else if (firstRow) secondRow = parseRowComponent(w);
2494
+ if (secondCell || secondCol || secondRow) {
2495
+ second = w;
2496
+ } else {
2497
+ i = save;
2498
+ }
2499
+ }
2500
+ if (second === null && formula[i] === "(") return;
2501
+ const dead = () => {
2502
+ if (formula[i] === "#") i++;
2503
+ replace(refStart, "#REF!");
2504
+ hasRefError = true;
2505
+ };
2506
+ if (second !== null) {
2507
+ if (!rewritable) return;
2508
+ if (firstCell && secondCell) {
2509
+ const a = firstCell, b = secondCell;
2510
+ const rows = isRowShift ? shiftSpan(a.row, b.row, shift) : { start: a.row, end: b.row };
2511
+ const cols = isRowShift ? { start: a.col, end: b.col } : shiftSpan(a.col, b.col, shift);
2512
+ if (!rows || !cols) {
2513
+ dead();
2514
+ return;
2515
+ }
2516
+ replace(refStart, `${emitCell(a, rows.start, cols.start)}:${emitCell(b, rows.end, cols.end)}`);
2517
+ return;
2518
+ }
2519
+ if (firstCol && secondCol) {
2520
+ if (isRowShift) return;
2521
+ const span = shiftSpan(firstCol.col, secondCol.col, shift);
2522
+ if (!span) {
2523
+ dead();
2524
+ return;
2525
+ }
2526
+ const a = firstCol.abs ? "$" : "";
2527
+ const b = secondCol.abs ? "$" : "";
2528
+ replace(refStart, `${a}${columnLetter(span.start)}:${b}${columnLetter(span.end)}`);
2529
+ return;
2530
+ }
2531
+ if (firstRow && secondRow) {
2532
+ if (!isRowShift) return;
2533
+ const span = shiftSpan(firstRow.row, secondRow.row, shift);
2534
+ if (!span) {
2535
+ dead();
2536
+ return;
2537
+ }
2538
+ const a = firstRow.abs ? "$" : "";
2539
+ const b = secondRow.abs ? "$" : "";
2540
+ replace(refStart, `${a}${span.start}:${b}${span.end}`);
2541
+ return;
2542
+ }
2543
+ return;
2544
+ }
2545
+ if (!firstCell || !rewritable) return;
2546
+ const row = isRowShift ? shiftIndex(firstCell.row, shift) : firstCell.row;
2547
+ const col = isRowShift ? firstCell.col : shiftIndex(firstCell.col, shift);
2548
+ if (row === null || col === null) {
2549
+ dead();
2550
+ return;
2551
+ }
2552
+ replace(refStart, emitCell(firstCell, row, col));
2553
+ };
2554
+ while (i < n) {
2555
+ const ch = formula[i];
2556
+ if (ch === '"') {
2557
+ let j = i + 1;
2558
+ while (j < n) {
2559
+ if (formula[j] === '"') {
2560
+ if (formula[j + 1] === '"') {
2561
+ j += 2;
2562
+ continue;
2563
+ }
2564
+ j++;
2565
+ break;
2566
+ }
2567
+ j++;
2568
+ }
2569
+ i = j;
2570
+ continue;
2571
+ }
2572
+ if (ch === "'") {
2573
+ const quoted = readQuoted();
2574
+ if (formula[i] === "!") {
2575
+ i++;
2576
+ const name = quoted.slice(1, -1);
2577
+ const rewritable = name.indexOf(":") === -1 && (name.indexOf("''") === -1 ? name : name.replace(/''/g, "'")).toUpperCase() === editedSheet;
2578
+ consumeRef(i, rewritable);
2579
+ }
2580
+ continue;
2581
+ }
2582
+ if (isWordChar(formula.charCodeAt(i))) {
2583
+ const start = i;
2584
+ const word = readWord();
2585
+ if (formula[i] === "!") {
2586
+ i++;
2587
+ consumeRef(i, word.toUpperCase() === editedSheet);
2588
+ continue;
2589
+ }
2590
+ if (formula[i] === ":") {
2591
+ const save = i;
2592
+ i++;
2593
+ const w2 = i < n && isWordChar(formula.charCodeAt(i)) ? readWord() : "";
2594
+ if (w2 && formula[i] === "!") {
2595
+ i++;
2596
+ consumeRef(i, false);
2597
+ continue;
2598
+ }
2599
+ i = save;
2600
+ }
2601
+ if (formula[i] === "[") {
2602
+ let depth = 0;
2603
+ let j = i;
2604
+ while (j < n) {
2605
+ if (formula[j] === "[") depth++;
2606
+ else if (formula[j] === "]" && --depth === 0) {
2607
+ j++;
2608
+ break;
2609
+ }
2610
+ j++;
2611
+ }
2612
+ i = j;
2613
+ continue;
2614
+ }
2615
+ i = start;
2616
+ consumeRef(start, unqualifiedRewritable);
2617
+ continue;
2618
+ }
2619
+ i++;
2620
+ }
2621
+ if (!changed) return { text: formula, changed: false, hasRefError };
2622
+ return { text: out + formula.slice(flushed), changed: true, hasRefError };
2623
+ }
2624
+
2178
2625
  // src/model/range.ts
2179
- var _sheet, _startRow, _startColumn, _endRow, _endColumn, _address, _Range_instances, forEach_fn;
2626
+ var _sheet, _startRow, _startColumn, _endRow, _endColumn, _address, _Range_instances, forEach_fn, forEachExisting_fn;
2180
2627
  var Range = class {
2181
2628
  constructor(sheet, rangeReference) {
2182
2629
  __privateAdd(this, _Range_instances);
@@ -2225,26 +2672,63 @@ var Range = class {
2225
2672
  * Gets all cell values as a 2D array (row-major order).
2226
2673
  */
2227
2674
  getValues() {
2228
- const result = [];
2229
- for (let r = 0; r < this.rowCount; r++) {
2230
- const row = [];
2231
- for (let c = 0; c < this.columnCount; c++) {
2232
- const cell = __privateGet(this, _sheet).tryGetCell(__privateGet(this, _startRow) + r, __privateGet(this, _startColumn) + c);
2233
- row.push(cell ? cell.getValue() : null);
2675
+ const rowCount = this.rowCount;
2676
+ const columnCount = this.columnCount;
2677
+ const result = new Array(rowCount);
2678
+ for (let r = 0; r < rowCount; r++) result[r] = new Array(columnCount).fill(null);
2679
+ const startRow = __privateGet(this, _startRow);
2680
+ const startColumn = __privateGet(this, _startColumn);
2681
+ __privateGet(this, _sheet).forEachCellIn(
2682
+ startRow,
2683
+ startColumn,
2684
+ __privateGet(this, _endRow),
2685
+ __privateGet(this, _endColumn),
2686
+ (cell, row, column) => {
2687
+ result[row - startRow][column - startColumn] = cell.getValue();
2234
2688
  }
2235
- result.push(row);
2236
- }
2689
+ );
2237
2690
  return result;
2238
2691
  }
2239
2692
  /** Clears all cells in the range (values only, preserves style). */
2240
2693
  clear() {
2241
- __privateMethod(this, _Range_instances, forEach_fn).call(this, (cell) => cell.clear());
2694
+ __privateMethod(this, _Range_instances, forEachExisting_fn).call(this, (cell) => cell.clear());
2242
2695
  }
2243
- /** Auto-fits all columns in the range. */
2244
- autoFit() {
2245
- for (let col = __privateGet(this, _startColumn); col <= __privateGet(this, _endColumn); col++) {
2246
- __privateGet(this, _sheet).autoFitColumn(col);
2696
+ /** Clears values AND styles of all cells in the range. */
2697
+ clearAll() {
2698
+ __privateMethod(this, _Range_instances, forEachExisting_fn).call(this, (cell) => cell.clearAll());
2699
+ }
2700
+ /**
2701
+ * Moves the whole range so its top-left cell lands on `destTopLeft`,
2702
+ * carrying values, formulas, styles and spill anchors. The vacated cells
2703
+ * are cleared and the destination block is fully replaced (empty source
2704
+ * cells clear their destination). Overlapping source/destination is safe.
2705
+ * Formula text is NOT adjusted for the new position.
2706
+ *
2707
+ * @example
2708
+ * ws.getRange('A1:B3').moveTo('D1');
2709
+ */
2710
+ moveTo(destTopLeft) {
2711
+ const dest = parseCellRef(destTopLeft.replace(/\$/g, "").toUpperCase());
2712
+ const deltaRow = dest.row - __privateGet(this, _startRow);
2713
+ const deltaCol = dest.column - __privateGet(this, _startColumn);
2714
+ if (deltaRow === 0 && deltaCol === 0) return;
2715
+ if (__privateGet(this, _endRow) + deltaRow > EXCEL_MAX_ROWS || __privateGet(this, _endColumn) + deltaCol > EXCEL_MAX_COLUMNS) {
2716
+ throw new RangeError("Destination extends past the sheet limits.");
2247
2717
  }
2718
+ const rowOrder = deltaRow > 0 ? { from: __privateGet(this, _endRow), to: __privateGet(this, _startRow), step: -1 } : { from: __privateGet(this, _startRow), to: __privateGet(this, _endRow), step: 1 };
2719
+ const colOrder = deltaCol > 0 ? { from: __privateGet(this, _endColumn), to: __privateGet(this, _startColumn), step: -1 } : { from: __privateGet(this, _startColumn), to: __privateGet(this, _endColumn), step: 1 };
2720
+ for (let r = rowOrder.from; deltaRow > 0 ? r >= rowOrder.to : r <= rowOrder.to; r += rowOrder.step) {
2721
+ for (let c = colOrder.from; deltaCol > 0 ? c >= colOrder.to : c <= colOrder.to; c += colOrder.step) {
2722
+ __privateGet(this, _sheet).moveCellRC(r, c, r + deltaRow, c + deltaCol);
2723
+ }
2724
+ }
2725
+ }
2726
+ /**
2727
+ * Auto-fits all columns in the range. Measures every column in one sparse
2728
+ * sweep rather than re-scanning the sheet per column.
2729
+ */
2730
+ autoFit() {
2731
+ __privateGet(this, _sheet).autoFitColumns(__privateGet(this, _startColumn), __privateGet(this, _endColumn));
2248
2732
  }
2249
2733
  // ── Style bulk operations ──────────────────────────────────────────────────
2250
2734
  /** Applies a CellStyle to all cells in the range. Returns this for chaining. */
@@ -2318,9 +2802,57 @@ forEach_fn = function(action) {
2318
2802
  }
2319
2803
  }
2320
2804
  };
2805
+ /**
2806
+ * Like #forEach but only visits cells that already exist — used by the
2807
+ * clearing operations so they never materialise empty cells, and so the cost
2808
+ * tracks the populated cells rather than the area of the range.
2809
+ */
2810
+ forEachExisting_fn = function(action) {
2811
+ __privateGet(this, _sheet).forEachCellIn(
2812
+ __privateGet(this, _startRow),
2813
+ __privateGet(this, _startColumn),
2814
+ __privateGet(this, _endRow),
2815
+ __privateGet(this, _endColumn),
2816
+ (cell) => action(cell)
2817
+ );
2818
+ };
2321
2819
 
2322
2820
  // src/model/worksheet.ts
2323
- var _sharedStrings2, _styles2, _name, _rows, _cols, _mergeCells, _pane, _autoFilter, _dataValidations, _visibility, _drawings, _preservedTail, _preservedRels, _Worksheet_instances, activeTail_fn, buildColsXml_fn, buildSheetDataXml_fn, buildMergeCellsXml_fn, buildDataValidationsXml_fn, buildSheetViewXml_fn, getOrCreateRow_fn;
2821
+ var PROTECTION_DEFAULTS = {
2822
+ allowSelectLockedCells: true,
2823
+ allowSelectUnlockedCells: true,
2824
+ allowFormatCells: false,
2825
+ allowFormatColumns: false,
2826
+ allowFormatRows: false,
2827
+ allowInsertRows: false,
2828
+ allowInsertColumns: false,
2829
+ allowInsertHyperlinks: false,
2830
+ allowDeleteRows: false,
2831
+ allowDeleteColumns: false,
2832
+ allowSort: false,
2833
+ allowAutoFilter: false,
2834
+ allowPivotTables: false,
2835
+ allowEditObjects: false,
2836
+ allowEditScenarios: false
2837
+ };
2838
+ var PROTECTION_ATTRS = [
2839
+ ["allowSelectLockedCells", "selectLockedCells"],
2840
+ ["allowSelectUnlockedCells", "selectUnlockedCells"],
2841
+ ["allowFormatCells", "formatCells"],
2842
+ ["allowFormatColumns", "formatColumns"],
2843
+ ["allowFormatRows", "formatRows"],
2844
+ ["allowInsertRows", "insertRows"],
2845
+ ["allowInsertColumns", "insertColumns"],
2846
+ ["allowInsertHyperlinks", "insertHyperlinks"],
2847
+ ["allowDeleteRows", "deleteRows"],
2848
+ ["allowDeleteColumns", "deleteColumns"],
2849
+ ["allowSort", "sort"],
2850
+ ["allowAutoFilter", "autoFilter"],
2851
+ ["allowPivotTables", "pivotTables"],
2852
+ ["allowEditObjects", "objects"],
2853
+ ["allowEditScenarios", "scenarios"]
2854
+ ];
2855
+ 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;
2324
2856
  var Worksheet = class {
2325
2857
  /** @internal */
2326
2858
  constructor(name, sharedStrings, styles) {
@@ -2336,6 +2868,26 @@ var Worksheet = class {
2336
2868
  __privateAdd(this, _dataValidations, []);
2337
2869
  __privateAdd(this, _visibility, "visible");
2338
2870
  __privateAdd(this, _drawings, []);
2871
+ __privateAdd(this, _protection);
2872
+ /**
2873
+ * Password/hash attributes of a protection element read from a file, kept
2874
+ * verbatim so a protected sheet round-trips with its password intact. This
2875
+ * library never creates them: see {@link protect}.
2876
+ */
2877
+ __privateAdd(this, _protectionCredentials, "");
2878
+ /** Hyperlinks keyed by their normalised ref, in insertion order. */
2879
+ __privateAdd(this, _hyperlinks, /* @__PURE__ */ new Map());
2880
+ /** Comments keyed by their normalised cell ref, in insertion order. */
2881
+ __privateAdd(this, _comments, /* @__PURE__ */ new Map());
2882
+ /**
2883
+ * True once the comment set has been modified through the API. Until then a
2884
+ * sheet loaded from a file keeps its original comments and VML parts verbatim,
2885
+ * so untouched files round-trip byte-for-byte; from then on this sheet owns
2886
+ * them and regenerates both. See {@link setComment}.
2887
+ */
2888
+ __privateAdd(this, _commentsDirty, false);
2889
+ /** Hyperlink ref → relationship id, awaiting resolution against the rels part. */
2890
+ __privateAdd(this, _pendingHyperlinkRels, /* @__PURE__ */ new Map());
2339
2891
  // Round-trip state captured when this sheet was loaded from a file: the
2340
2892
  // relationship-bearing leaf elements of the worksheet part, and the sheet's
2341
2893
  // original relationships. Re-emitted on save so drawings, images and legacy
@@ -2355,8 +2907,15 @@ var Worksheet = class {
2355
2907
  __privateSet(this, _name, value);
2356
2908
  }
2357
2909
  getCell(refOrRow, column) {
2358
- const ref = typeof refOrRow === "string" ? refOrRow.replace(/\$/g, "").toUpperCase() : cellRefFromRowCol(refOrRow, column);
2359
- const { row } = parseCellRef(ref);
2910
+ let ref;
2911
+ let row;
2912
+ if (typeof refOrRow === "string") {
2913
+ ref = normalizeRefFast(refOrRow);
2914
+ row = parseCellRef(ref).row;
2915
+ } else {
2916
+ ref = cellRefFromRowCol(refOrRow, column);
2917
+ row = refOrRow;
2918
+ }
2360
2919
  const rowData = __privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, row);
2361
2920
  let cellData = rowData.cells.get(ref);
2362
2921
  if (!cellData) {
@@ -2366,10 +2925,17 @@ var Worksheet = class {
2366
2925
  return new Cell(cellData, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2));
2367
2926
  }
2368
2927
  tryGetCell(refOrRow, column) {
2369
- const ref = typeof refOrRow === "string" ? refOrRow.replace(/\$/g, "").toUpperCase() : cellRefFromRowCol(refOrRow, column);
2370
- const { row } = parseCellRef(ref);
2371
- const rowData = __privateGet(this, _rows).get(row);
2372
- if (!rowData) return null;
2928
+ let rowData;
2929
+ let ref;
2930
+ if (typeof refOrRow === "string") {
2931
+ ref = normalizeRefFast(refOrRow);
2932
+ rowData = __privateGet(this, _rows).get(parseCellRef(ref).row);
2933
+ if (!rowData) return null;
2934
+ } else {
2935
+ rowData = __privateGet(this, _rows).get(refOrRow);
2936
+ if (!rowData) return null;
2937
+ ref = cellRefFromRowCol(refOrRow, column);
2938
+ }
2373
2939
  const cellData = rowData.cells.get(ref);
2374
2940
  if (!cellData) return null;
2375
2941
  return new Cell(cellData, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2));
@@ -2394,10 +2960,83 @@ var Worksheet = class {
2394
2960
  getSpillRange(reference) {
2395
2961
  return this.tryGetCell(reference)?.getArrayRef() ?? null;
2396
2962
  }
2963
+ /**
2964
+ * Iterates the populated rows of the sheet in ascending row order, yielding
2965
+ * each row's 1-based index and its cells (left to right). Rows and cells
2966
+ * that were never written are skipped, so a sparse sheet iterates in
2967
+ * O(populated cells) regardless of its bounds.
2968
+ *
2969
+ * @example
2970
+ * for (const { rowIndex, cells } of ws.rows()) {
2971
+ * for (const cell of cells) console.log(rowIndex, cell.reference, cell.getValue());
2972
+ * }
2973
+ */
2974
+ *rows() {
2975
+ const populated = [...__privateGet(this, _rows).values()].filter((row) => row.cells.size > 0).sort((a, b) => a.rowIndex - b.rowIndex);
2976
+ for (const row of populated) {
2977
+ 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)));
2978
+ yield { rowIndex: row.rowIndex, cells };
2979
+ }
2980
+ }
2981
+ /**
2982
+ * Iterates every populated cell of the sheet in row-major order. Sparse
2983
+ * counterpart to scanning {@link usedBounds} with {@link tryGetCell}.
2984
+ *
2985
+ * @example
2986
+ * for (const cell of ws.cells()) console.log(cell.reference, cell.getValue());
2987
+ */
2988
+ *cells() {
2989
+ for (const { cells } of this.rows()) yield* cells;
2990
+ }
2991
+ /**
2992
+ * @internal Visits the populated cells inside a rectangle, passing the
2993
+ * coordinates it already knows so the callback never has to parse a
2994
+ * reference. Backs the Range operations that must not create cells; probing
2995
+ * every coordinate would cost O(area) even on an empty sheet.
2996
+ */
2997
+ forEachCellIn(startRow, startColumn, endRow, endColumn, action) {
2998
+ const visit = (row) => {
2999
+ for (const cd of row.cells.values()) {
3000
+ const col = columnFromRef(cd.reference, refSplit(cd.reference));
3001
+ if (col < startColumn || col > endColumn) continue;
3002
+ action(new Cell(cd, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2)), row.rowIndex, col);
3003
+ }
3004
+ };
3005
+ if (endRow - startRow + 1 <= __privateGet(this, _rows).size) {
3006
+ for (let r = startRow; r <= endRow; r++) {
3007
+ const row = __privateGet(this, _rows).get(r);
3008
+ if (row) visit(row);
3009
+ }
3010
+ return;
3011
+ }
3012
+ for (const row of __privateGet(this, _rows).values()) {
3013
+ if (row.rowIndex >= startRow && row.rowIndex <= endRow) visit(row);
3014
+ }
3015
+ }
3016
+ /**
3017
+ * @internal Moves a cell by coordinates, skipping the reference parsing that
3018
+ * the public {@link moveCell} has to do. Used by bulk range moves.
3019
+ */
3020
+ moveCellRC(fromRow, fromColumn, toRow, toColumn) {
3021
+ if (fromRow === toRow && fromColumn === toColumn) return;
3022
+ const fromRef = cellRefFromRowCol(fromRow, fromColumn);
3023
+ const toRef = cellRefFromRowCol(toRow, toColumn);
3024
+ const src = __privateMethod(this, _Worksheet_instances, takeCellDataAt_fn).call(this, fromRow, fromRef);
3025
+ if (!src) {
3026
+ __privateMethod(this, _Worksheet_instances, takeCellDataAt_fn).call(this, toRow, toRef);
3027
+ return;
3028
+ }
3029
+ const moved = { ...src, reference: toRef };
3030
+ if (moved.arrayRef) {
3031
+ moved.arrayRef = translateRangeRef(moved.arrayRef, toRow - fromRow, toColumn - fromColumn);
3032
+ }
3033
+ __privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, toRow).cells.set(toRef, moved);
3034
+ }
2397
3035
  /**
2398
3036
  * Returns the 1-based extent of populated cells (largest row and column that
2399
3037
  * contain data). Returns zeros for an empty sheet. Useful for snapshotting a
2400
- * sheet into a dense 2D array.
3038
+ * sheet into a dense 2D array — though {@link rows} and {@link cells} are
3039
+ * cheaper when a dense shape is not actually required.
2401
3040
  */
2402
3041
  get usedBounds() {
2403
3042
  let rowCount = 0;
@@ -2406,7 +3045,7 @@ var Worksheet = class {
2406
3045
  if (row.cells.size === 0) continue;
2407
3046
  if (row.rowIndex > rowCount) rowCount = row.rowIndex;
2408
3047
  for (const ref of row.cells.keys()) {
2409
- const col = parseCellRef(ref).column;
3048
+ const col = columnFromRef(ref, refSplit(ref));
2410
3049
  if (col > columnCount) columnCount = col;
2411
3050
  }
2412
3051
  }
@@ -2417,9 +3056,41 @@ var Worksheet = class {
2417
3056
  setColumnWidth(columnIndex, width) {
2418
3057
  if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
2419
3058
  if (width <= 0) throw new RangeError("Width must be > 0.");
2420
- __privateSet(this, _cols, __privateGet(this, _cols).filter((c) => !(c.min <= columnIndex && c.max >= columnIndex)));
2421
- __privateGet(this, _cols).push({ min: columnIndex, max: columnIndex, width, customWidth: true });
2422
- __privateGet(this, _cols).sort((a, b) => a.min - b.min);
3059
+ 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 });
3060
+ def.width = width;
3061
+ def.customWidth = true;
3062
+ }
3063
+ /**
3064
+ * Removes the explicit width of a column so it falls back to the sheet
3065
+ * default. Counterpart to {@link setColumnWidth}; afterwards
3066
+ * {@link getColumnWidth} returns `undefined`. A hidden column stays hidden.
3067
+ */
3068
+ resetColumnWidth(columnIndex) {
3069
+ if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
3070
+ const def = __privateMethod(this, _Worksheet_instances, splitColSpan_fn).call(this, columnIndex);
3071
+ if (!def) return;
3072
+ def.width = void 0;
3073
+ def.customWidth = false;
3074
+ __privateMethod(this, _Worksheet_instances, pruneColDefs_fn).call(this);
3075
+ }
3076
+ /**
3077
+ * Shows or hides a column (1-based index). Hidden columns round-trip to the
3078
+ * file as `<col hidden="1">` and keep any explicit width.
3079
+ */
3080
+ setColumnHidden(columnIndex, hidden = true) {
3081
+ if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
3082
+ const def = __privateMethod(this, _Worksheet_instances, splitColSpan_fn).call(this, columnIndex);
3083
+ if (def) {
3084
+ def.hidden = hidden;
3085
+ __privateMethod(this, _Worksheet_instances, pruneColDefs_fn).call(this);
3086
+ } else if (hidden) {
3087
+ __privateMethod(this, _Worksheet_instances, insertColDef_fn).call(this, { min: columnIndex, max: columnIndex, customWidth: false, hidden: true });
3088
+ }
3089
+ }
3090
+ /** True if the given 1-based column is hidden. */
3091
+ isColumnHidden(columnIndex) {
3092
+ if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
3093
+ return __privateMethod(this, _Worksheet_instances, findColDef_fn).call(this, columnIndex)?.hidden === true;
2423
3094
  }
2424
3095
  /**
2425
3096
  * Returns the explicit width of a column (1-based index), or `undefined` when
@@ -2429,22 +3100,28 @@ var Worksheet = class {
2429
3100
  */
2430
3101
  getColumnWidth(columnIndex) {
2431
3102
  if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
2432
- return __privateGet(this, _cols).find((c) => c.min <= columnIndex && c.max >= columnIndex)?.width;
3103
+ return __privateMethod(this, _Worksheet_instances, findColDef_fn).call(this, columnIndex)?.width;
2433
3104
  }
2434
3105
  /** Auto-fits column width based on content (approximate). */
2435
3106
  autoFitColumn(columnIndex) {
2436
- if (columnIndex < 1) throw new RangeError("Column index must be >= 1.");
2437
- let maxLen = 8;
2438
- for (const row of __privateGet(this, _rows).values()) {
2439
- const ref = cellRefFromRowCol(row.rowIndex, columnIndex);
2440
- const cellData = row.cells.get(ref);
2441
- if (cellData) {
2442
- const cell = new Cell(cellData, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2));
2443
- const textLen = cell.getText().length;
2444
- if (textLen > maxLen) maxLen = textLen;
2445
- }
3107
+ this.autoFitColumns(columnIndex, columnIndex);
3108
+ }
3109
+ /**
3110
+ * Auto-fits an inclusive span of columns (1-based) in a single pass over the
3111
+ * populated cells, instead of re-scanning the sheet once per column.
3112
+ */
3113
+ autoFitColumns(firstColumn, lastColumn) {
3114
+ if (firstColumn < 1) throw new RangeError("Column index must be >= 1.");
3115
+ if (lastColumn < firstColumn) return;
3116
+ const maxLen = /* @__PURE__ */ new Map();
3117
+ this.forEachCellIn(1, firstColumn, EXCEL_MAX_ROWS, lastColumn, (cell, _row, column) => {
3118
+ const len = cell.getText().length;
3119
+ if (len > (maxLen.get(column) ?? 0)) maxLen.set(column, len);
3120
+ });
3121
+ for (let col = firstColumn; col <= lastColumn; col++) {
3122
+ const len = Math.max(maxLen.get(col) ?? 0, 8);
3123
+ this.setColumnWidth(col, Math.min(len * 1.2 + 2, 255));
2446
3124
  }
2447
- this.setColumnWidth(columnIndex, Math.min(maxLen * 1.2 + 2, 255));
2448
3125
  }
2449
3126
  /** Sets the height of a row (1-based index). */
2450
3127
  setRowHeight(rowIndex, height) {
@@ -2462,6 +3139,19 @@ var Worksheet = class {
2462
3139
  if (rowIndex < 1) throw new RangeError("Row index must be >= 1.");
2463
3140
  return __privateGet(this, _rows).get(rowIndex)?.height;
2464
3141
  }
3142
+ /**
3143
+ * Removes the explicit height of a row so it falls back to the sheet
3144
+ * default. Counterpart to {@link setRowHeight}; afterwards
3145
+ * {@link getRowHeight} returns `undefined`. A hidden row stays hidden.
3146
+ */
3147
+ resetRowHeight(rowIndex) {
3148
+ if (rowIndex < 1) throw new RangeError("Row index must be >= 1.");
3149
+ const row = __privateGet(this, _rows).get(rowIndex);
3150
+ if (!row) return;
3151
+ row.height = void 0;
3152
+ row.customHeight = void 0;
3153
+ if (row.cells.size === 0 && !row.hidden) __privateGet(this, _rows).delete(rowIndex);
3154
+ }
2465
3155
  /** Shows or hides a row (1-based index). Hidden rows affect SUBTOTAL(101-111). */
2466
3156
  setRowHidden(rowIndex, hidden = true) {
2467
3157
  if (rowIndex < 1) throw new RangeError("Row index must be >= 1.");
@@ -2471,6 +3161,137 @@ var Worksheet = class {
2471
3161
  isRowHidden(rowIndex) {
2472
3162
  return __privateGet(this, _rows).get(rowIndex)?.hidden === true;
2473
3163
  }
3164
+ /**
3165
+ * The 1-based indices of every hidden row, ascending. Sparse counterpart to
3166
+ * probing {@link isRowHidden} across {@link usedBounds}.
3167
+ */
3168
+ get hiddenRows() {
3169
+ const result = [];
3170
+ for (const row of __privateGet(this, _rows).values()) {
3171
+ if (row.hidden) result.push(row.rowIndex);
3172
+ }
3173
+ return result.sort((a, b) => a - b);
3174
+ }
3175
+ /**
3176
+ * The 1-based indices of every hidden column, ascending. Counterpart to
3177
+ * {@link isColumnHidden}, in the same spirit as {@link hiddenRows}.
3178
+ */
3179
+ get hiddenColumns() {
3180
+ const result = [];
3181
+ for (const def of __privateGet(this, _cols)) {
3182
+ if (!def.hidden) continue;
3183
+ for (let c = def.min; c <= def.max; c++) result.push(c);
3184
+ }
3185
+ return result.sort((a, b) => a - b);
3186
+ }
3187
+ // ── Structural editing ─────────────────────────────────────────────────────
3188
+ /**
3189
+ * Inserts `count` empty rows starting at row `at` (1-based). Everything at
3190
+ * or below `at` moves down: cell values, formulas (references are rewritten
3191
+ * the way Excel does), styles, row heights, hidden flags, merges,
3192
+ * autofilter and data-validation ranges.
3193
+ *
3194
+ * Frozen panes are a view property and stay put. Drawing/chart anchors
3195
+ * preserved from a loaded file are opaque XML and do NOT shift — images
3196
+ * keep their absolute position (known limitation).
3197
+ *
3198
+ * When formulas on OTHER sheets reference this sheet, use the
3199
+ * Workbook-level counterpart so they are rewritten too, or call
3200
+ * `applyExternalShift` on each sheet yourself. If a RecalcEngine is
3201
+ * attached, notify it afterwards (e.g. `engine.onRowsInserted(...)`).
3202
+ *
3203
+ * @example
3204
+ * ws.insertRows(3, 2); // pushes row 3 and below down by two rows
3205
+ */
3206
+ insertRows(at, count = 1) {
3207
+ return __privateMethod(this, _Worksheet_instances, applyStructural_fn).call(this, "row", "insert", at, count);
3208
+ }
3209
+ /**
3210
+ * Deletes `count` rows starting at row `at` (1-based). Rows below move up;
3211
+ * formulas referencing the deleted band get `#REF!` (fully inside) or
3212
+ * shrink (partially inside), exactly like Excel. See {@link insertRows}
3213
+ * for the shifting rules and known limitations.
3214
+ */
3215
+ deleteRows(at, count = 1) {
3216
+ return __privateMethod(this, _Worksheet_instances, applyStructural_fn).call(this, "row", "delete", at, count);
3217
+ }
3218
+ /**
3219
+ * Inserts `count` empty columns starting at column `at` (1-based).
3220
+ * Column-level counterpart to {@link insertRows}; widths and hidden flags
3221
+ * shift with the columns.
3222
+ */
3223
+ insertColumns(at, count = 1) {
3224
+ return __privateMethod(this, _Worksheet_instances, applyStructural_fn).call(this, "col", "insert", at, count);
3225
+ }
3226
+ /**
3227
+ * Deletes `count` columns starting at column `at` (1-based).
3228
+ * Column-level counterpart to {@link deleteRows}.
3229
+ */
3230
+ deleteColumns(at, count = 1) {
3231
+ return __privateMethod(this, _Worksheet_instances, applyStructural_fn).call(this, "col", "delete", at, count);
3232
+ }
3233
+ /**
3234
+ * @internal Rewrites formulas on THIS sheet after a structural edit on a
3235
+ * DIFFERENT sheet (named by `shift.sheetName`), so qualified references
3236
+ * like `'Data'!A5` stay correct. Cell positions here do not change.
3237
+ */
3238
+ applyExternalShift(shift) {
3239
+ return { refErrors: __privateMethod(this, _Worksheet_instances, rewriteFormulas_fn).call(this, shift) };
3240
+ }
3241
+ /**
3242
+ * Moves a cell's full contents — value, formula, style and spill anchor —
3243
+ * to another cell, overwriting the destination and clearing the source.
3244
+ * A missing source clears the destination. This is a literal move: formula
3245
+ * text is NOT adjusted for the new position.
3246
+ *
3247
+ * @example
3248
+ * ws.moveCell('A1', 'C5');
3249
+ */
3250
+ moveCell(from, to) {
3251
+ const fromRef = normalizeRef(from);
3252
+ const toRef = normalizeRef(to);
3253
+ if (fromRef === toRef) return;
3254
+ const src = __privateMethod(this, _Worksheet_instances, takeCellData_fn).call(this, fromRef);
3255
+ if (!src) {
3256
+ __privateMethod(this, _Worksheet_instances, deleteCellData_fn).call(this, toRef);
3257
+ return;
3258
+ }
3259
+ const fromPos = parseCellRef(fromRef);
3260
+ const toPos = parseCellRef(toRef);
3261
+ const moved = { ...src, reference: toRef };
3262
+ if (moved.arrayRef) {
3263
+ moved.arrayRef = translateRangeRef(
3264
+ moved.arrayRef,
3265
+ toPos.row - fromPos.row,
3266
+ toPos.column - fromPos.column
3267
+ );
3268
+ }
3269
+ __privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, toPos.row).cells.set(toRef, moved);
3270
+ }
3271
+ /**
3272
+ * Copies a cell's value, formula and style to another cell, overwriting the
3273
+ * destination and leaving the source untouched. A missing source clears the
3274
+ * destination. This is a literal copy: formula references are NOT adjusted,
3275
+ * and the spill anchor (`arrayRef`) is not copied — a recalc engine
3276
+ * re-establishes it.
3277
+ *
3278
+ * @example
3279
+ * ws.copyCell('A1', 'C5');
3280
+ */
3281
+ copyCell(from, to) {
3282
+ const fromRef = normalizeRef(from);
3283
+ const toRef = normalizeRef(to);
3284
+ if (fromRef === toRef) return;
3285
+ const srcRow = __privateGet(this, _rows).get(parseCellRef(fromRef).row);
3286
+ const src = srcRow?.cells.get(fromRef);
3287
+ if (!src) {
3288
+ __privateMethod(this, _Worksheet_instances, deleteCellData_fn).call(this, toRef);
3289
+ return;
3290
+ }
3291
+ const toPos = parseCellRef(toRef);
3292
+ const copied = { ...src, reference: toRef, arrayRef: void 0 };
3293
+ __privateMethod(this, _Worksheet_instances, getOrCreateRow_fn).call(this, toPos.row).cells.set(toRef, copied);
3294
+ }
2474
3295
  /** Merges cells in the given range (e.g. "A1:B2"). */
2475
3296
  mergeCells(rangeReference) {
2476
3297
  if (!rangeReference) throw new Error("Range reference cannot be empty.");
@@ -2517,6 +3338,119 @@ var Worksheet = class {
2517
3338
  get visibility() {
2518
3339
  return __privateGet(this, _visibility);
2519
3340
  }
3341
+ // ── Sheet protection ───────────────────────────────────────────────────────
3342
+ /**
3343
+ * Turns on sheet protection, optionally relaxing individual permissions.
3344
+ * Cells are locked by default in Excel, so protecting a sheet makes all of
3345
+ * them read-only; clear a cell style's `locked` flag to leave a cell editable.
3346
+ *
3347
+ * Excel's sheet protection is a UI guard, **not** security: the contents stay
3348
+ * readable to anything that opens the file. This library therefore never
3349
+ * creates a protection password. A password read from an existing file is
3350
+ * preserved so the sheet round-trips unchanged.
3351
+ *
3352
+ * @example
3353
+ * ws.protect(); // lock everything
3354
+ * ws.protect({ allowSort: true, allowAutoFilter: true });
3355
+ */
3356
+ protect(options) {
3357
+ __privateSet(this, _protection, { ...PROTECTION_DEFAULTS, ...options });
3358
+ }
3359
+ /** Turns off sheet protection, discarding any password read from a file. */
3360
+ unprotect() {
3361
+ __privateSet(this, _protection, void 0);
3362
+ __privateSet(this, _protectionCredentials, "");
3363
+ }
3364
+ /** True when this sheet is protected. */
3365
+ get isProtected() {
3366
+ return __privateGet(this, _protection) !== void 0;
3367
+ }
3368
+ /** The active permissions, or `undefined` when the sheet is not protected. */
3369
+ get protection() {
3370
+ return __privateGet(this, _protection) ? { ...__privateGet(this, _protection) } : void 0;
3371
+ }
3372
+ // ── Hyperlinks ─────────────────────────────────────────────────────────────
3373
+ /**
3374
+ * Attaches a hyperlink to a cell or range. Give `target` for an external
3375
+ * destination or `location` for one inside the workbook; passing both keeps
3376
+ * the external target and uses the location as its fragment, which is how
3377
+ * Excel links to an anchor within a document.
3378
+ *
3379
+ * A cell's displayed text still comes from its own value — set that
3380
+ * separately; `display` only overrides what Excel shows in the edit bar.
3381
+ *
3382
+ * @example
3383
+ * ws.getCell('A1').setValue('Anthropic');
3384
+ * ws.setHyperlink('A1', { target: 'https://anthropic.com', tooltip: 'Open site' });
3385
+ * ws.setHyperlink('B1', { location: "'Sheet2'!A1" });
3386
+ */
3387
+ setHyperlink(ref, link) {
3388
+ if (!link.target && !link.location) {
3389
+ throw new Error("A hyperlink needs a target (external) or a location (in-workbook).");
3390
+ }
3391
+ const normalized = normalizeRangeRef(ref);
3392
+ __privateGet(this, _hyperlinks).set(normalized, { ref: normalized, ...link });
3393
+ }
3394
+ /** The hyperlink covering exactly this ref, or undefined. */
3395
+ getHyperlink(ref) {
3396
+ return __privateGet(this, _hyperlinks).get(normalizeRangeRef(ref));
3397
+ }
3398
+ /** Removes the hyperlink on this ref. Returns true if one was there. */
3399
+ removeHyperlink(ref) {
3400
+ return __privateGet(this, _hyperlinks).delete(normalizeRangeRef(ref));
3401
+ }
3402
+ /** Every hyperlink on the sheet, including ones read from a file. */
3403
+ get hyperlinks() {
3404
+ return [...__privateGet(this, _hyperlinks).values()];
3405
+ }
3406
+ // ── Comments (notes) ───────────────────────────────────────────────────────
3407
+ /**
3408
+ * Attaches a comment (a "note" in current Excel versions) to a cell,
3409
+ * replacing any comment already there.
3410
+ *
3411
+ * Note that writing comments makes this sheet regenerate its comment and
3412
+ * legacy-drawing parts on save. If the file was opened with other legacy
3413
+ * drawing content on the same sheet — form controls, for instance — that
3414
+ * content is not reproduced. Reading comments has no such effect.
3415
+ *
3416
+ * @example
3417
+ * ws.setComment('B4', 'Check this figure', { author: 'Bill' });
3418
+ */
3419
+ setComment(ref, text, options) {
3420
+ const normalized = normalizeRef(ref);
3421
+ __privateGet(this, _comments).set(normalized, {
3422
+ ref: normalized,
3423
+ text,
3424
+ author: options?.author ?? ""
3425
+ });
3426
+ __privateSet(this, _commentsDirty, true);
3427
+ }
3428
+ /** The comment on a cell, or undefined. */
3429
+ getComment(ref) {
3430
+ return __privateGet(this, _comments).get(normalizeRef(ref));
3431
+ }
3432
+ /** Removes the comment on a cell. Returns true if one was there. */
3433
+ removeComment(ref) {
3434
+ const removed = __privateGet(this, _comments).delete(normalizeRef(ref));
3435
+ if (removed) __privateSet(this, _commentsDirty, true);
3436
+ return removed;
3437
+ }
3438
+ /** Every comment on the sheet, including ones read from a file. */
3439
+ get comments() {
3440
+ return [...__privateGet(this, _comments).values()];
3441
+ }
3442
+ /** @internal True when this sheet must write its own comment parts. */
3443
+ get ownsCommentParts() {
3444
+ return __privateGet(this, _commentsDirty) && __privateGet(this, _comments).size > 0;
3445
+ }
3446
+ /**
3447
+ * @internal True when the sheet took over its comment parts and any preserved
3448
+ * originals must be dropped — including the case where every comment was
3449
+ * deleted and nothing replaces them.
3450
+ */
3451
+ get commentPartsReplaced() {
3452
+ return __privateGet(this, _commentsDirty);
3453
+ }
2520
3454
  // ── Drawings (charts, images …) ──────────────────────────────────────────────
2521
3455
  /**
2522
3456
  * Attaches a drawing provider (e.g. a chart) to this worksheet.
@@ -2554,7 +3488,12 @@ var Worksheet = class {
2554
3488
  */
2555
3489
  get preservedRelationships() {
2556
3490
  const liveIds = new Set(__privateMethod(this, _Worksheet_instances, activeTail_fn).call(this).map((t) => t.relId));
2557
- return __privateGet(this, _preservedRels).filter((r) => liveIds.has(r.id));
3491
+ return __privateGet(this, _preservedRels).filter((r) => {
3492
+ if (r.type === REL_TYPES.comments) {
3493
+ return !this.commentPartsReplaced && __privateGet(this, _comments).size > 0;
3494
+ }
3495
+ return liveIds.has(r.id);
3496
+ });
2558
3497
  }
2559
3498
  /**
2560
3499
  * @internal Package paths of relationships that were preserved at load time
@@ -2571,35 +3510,86 @@ var Worksheet = class {
2571
3510
  * clear every preserved id so the two sets cannot collide.
2572
3511
  */
2573
3512
  get drawingRelId() {
2574
- let max = 0;
2575
- for (const r of __privateGet(this, _preservedRels)) {
2576
- const n = /^rId(\d+)$/.exec(r.id);
2577
- if (n) max = Math.max(max, parseInt(n[1], 10));
3513
+ return this.generatedRels().drawing ?? `rId${__privateMethod(this, _Worksheet_instances, relIdBase_fn).call(this) + 1}`;
3514
+ }
3515
+ /**
3516
+ * @internal Relationship ids for the parts this sheet generates itself.
3517
+ *
3518
+ * Derived purely from the model so the worksheet XML and the sheet's rels part
3519
+ * — which are written by different code — cannot disagree about an id. Order is
3520
+ * fixed: drawing, then external hyperlinks in insertion order, then the
3521
+ * comments part and its legacy VML drawing.
3522
+ */
3523
+ generatedRels() {
3524
+ let next = __privateMethod(this, _Worksheet_instances, relIdBase_fn).call(this);
3525
+ const result = { hyperlinks: /* @__PURE__ */ new Map() };
3526
+ if (this.hasDrawings) result.drawing = `rId${++next}`;
3527
+ for (const link of __privateGet(this, _hyperlinks).values()) {
3528
+ if (link.target) result.hyperlinks.set(link.ref, `rId${++next}`);
2578
3529
  }
2579
- return `rId${max + 1}`;
3530
+ if (this.ownsCommentParts) {
3531
+ result.comments = `rId${++next}`;
3532
+ result.vmlDrawing = `rId${++next}`;
3533
+ }
3534
+ return result;
2580
3535
  }
2581
3536
  /** @internal Captures round-trip state from the source package. */
2582
3537
  loadPreservedState(worksheetXml, relationships) {
2583
- __privateSet(this, _preservedRels, [...relationships]);
2584
- const known = new Set(relationships.map((r) => r.id));
3538
+ __privateSet(this, _preservedRels, relationships.filter((r) => r.type !== REL_TYPES.hyperlink));
3539
+ const known = new Set(__privateGet(this, _preservedRels).map((r) => r.id));
2585
3540
  __privateSet(this, _preservedTail, parseTailElements(worksheetXml).filter((t) => known.has(t.relId)));
3541
+ const byId = new Map(relationships.map((r) => [r.id, r]));
3542
+ for (const [ref, relId] of __privateGet(this, _pendingHyperlinkRels)) {
3543
+ const rel = byId.get(relId);
3544
+ const link = __privateGet(this, _hyperlinks).get(ref);
3545
+ if (!rel || !link) continue;
3546
+ __privateGet(this, _hyperlinks).set(ref, { ...link, target: rel.target });
3547
+ }
3548
+ __privateGet(this, _pendingHyperlinkRels).clear();
3549
+ for (const [ref, link] of [...__privateGet(this, _hyperlinks)]) {
3550
+ if (!link.target && !link.location) __privateGet(this, _hyperlinks).delete(ref);
3551
+ }
3552
+ }
3553
+ /**
3554
+ * @internal Package paths of the comment parts this sheet read at load time.
3555
+ * The save pipeline drops them from the preserved set once the sheet takes
3556
+ * ownership, so they are not written twice.
3557
+ */
3558
+ commentPartPaths() {
3559
+ const paths = [];
3560
+ for (const rel of __privateGet(this, _preservedRels)) {
3561
+ if (!rel.resolved) continue;
3562
+ if (rel.type === REL_TYPES.comments || rel.type === REL_TYPES.vmlDrawing) {
3563
+ paths.push(rel.resolved);
3564
+ }
3565
+ }
3566
+ return paths;
3567
+ }
3568
+ /**
3569
+ * @internal The relationship pointing at this sheet's comments part, if the
3570
+ * sheet was loaded from a file that had one.
3571
+ */
3572
+ commentsPartPath() {
3573
+ return __privateGet(this, _preservedRels).find((r) => r.type === REL_TYPES.comments)?.resolved;
2586
3574
  }
2587
3575
  // ── XML serialisation ──────────────────────────────────────────────────────
2588
3576
  /** Builds the xl/worksheets/sheetN.xml string. */
2589
3577
  buildXml() {
3578
+ const rels = this.generatedRels();
3579
+ const sheetViewXml = __privateMethod(this, _Worksheet_instances, buildSheetViewXml_fn).call(this);
2590
3580
  const colsXml = __privateMethod(this, _Worksheet_instances, buildColsXml_fn).call(this);
2591
3581
  const sheetDataXml = __privateMethod(this, _Worksheet_instances, buildSheetDataXml_fn).call(this);
2592
- const mergeXml = __privateMethod(this, _Worksheet_instances, buildMergeCellsXml_fn).call(this);
3582
+ const protectionXml = __privateMethod(this, _Worksheet_instances, buildSheetProtectionXml_fn).call(this);
2593
3583
  const filterXml = __privateGet(this, _autoFilter) ? `<autoFilter ref="${__privateGet(this, _autoFilter).ref}"/>` : "";
3584
+ const mergeXml = __privateMethod(this, _Worksheet_instances, buildMergeCellsXml_fn).call(this);
2594
3585
  const validXml = __privateMethod(this, _Worksheet_instances, buildDataValidationsXml_fn).call(this);
2595
- const sheetViewXml = __privateMethod(this, _Worksheet_instances, buildSheetViewXml_fn).call(this);
2596
- const preservedTailXml = __privateMethod(this, _Worksheet_instances, activeTail_fn).call(this).map((t) => t.xml).join("");
2597
- const drawingXml = this.hasDrawings ? `<drawing r:id="${this.drawingRelId}"/>` : "";
3586
+ const linksXml = __privateMethod(this, _Worksheet_instances, buildHyperlinksXml_fn).call(this, rels.hyperlinks);
3587
+ const tailXml = __privateMethod(this, _Worksheet_instances, buildTailXml_fn).call(this, rels);
2598
3588
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2599
3589
  <worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
2600
3590
  xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
2601
3591
  <sheetViews>${sheetViewXml}</sheetViews>
2602
- ${colsXml}${sheetDataXml}${filterXml}${mergeXml}${validXml}${preservedTailXml}${drawingXml}
3592
+ ${colsXml}${sheetDataXml}${protectionXml}${filterXml}${mergeXml}${validXml}${linksXml}${tailXml}
2603
3593
  </worksheet>`;
2604
3594
  }
2605
3595
  /** Parses xl/worksheets/sheetN.xml and loads cells into memory. */
@@ -2610,12 +3600,17 @@ ${colsXml}${sheetDataXml}${filterXml}${mergeXml}${validXml}${preservedTailXml}${
2610
3600
  __privateSet(this, _pane, void 0);
2611
3601
  __privateSet(this, _autoFilter, void 0);
2612
3602
  __privateSet(this, _dataValidations, []);
3603
+ __privateSet(this, _protection, void 0);
3604
+ __privateSet(this, _protectionCredentials, "");
3605
+ __privateGet(this, _hyperlinks).clear();
3606
+ __privateGet(this, _pendingHyperlinkRels).clear();
2613
3607
  const root = parseXml(xml2);
2614
3608
  for (const colEl of getElementsByTagName(root, "col")) {
3609
+ const widthAttr = getAttr(colEl, "width");
2615
3610
  __privateGet(this, _cols).push({
2616
3611
  min: parseInt(getAttr(colEl, "min") ?? "1", 10),
2617
3612
  max: parseInt(getAttr(colEl, "max") ?? "1", 10),
2618
- width: parseFloat(getAttr(colEl, "width") ?? "8"),
3613
+ width: widthAttr !== void 0 ? parseFloat(widthAttr) : void 0,
2619
3614
  customWidth: getAttr(colEl, "customWidth") === "1",
2620
3615
  hidden: getAttr(colEl, "hidden") === "1"
2621
3616
  });
@@ -2667,6 +3662,75 @@ ${colsXml}${sheetDataXml}${filterXml}${mergeXml}${validXml}${preservedTailXml}${
2667
3662
  const formula = f1Els[0]?.textContent ?? "";
2668
3663
  if (sqref && formula) __privateGet(this, _dataValidations).push({ sqref, formula });
2669
3664
  }
3665
+ const protEl = getElementsByTagName(root, "sheetProtection")[0];
3666
+ if (protEl && getAttr(protEl, "sheet") === "1") {
3667
+ const protection = { ...PROTECTION_DEFAULTS };
3668
+ for (const [key, attr] of PROTECTION_ATTRS) {
3669
+ const raw = getAttr(protEl, attr);
3670
+ if (raw !== void 0) protection[key] = raw !== "1";
3671
+ }
3672
+ __privateSet(this, _protection, protection);
3673
+ let credentials = "";
3674
+ for (const attr of ["password", "algorithmName", "hashValue", "saltValue", "spinCount"]) {
3675
+ const raw = getAttr(protEl, attr);
3676
+ if (raw !== void 0) credentials += ` ${attr}="${escXmlAttr(raw)}"`;
3677
+ }
3678
+ __privateSet(this, _protectionCredentials, credentials);
3679
+ }
3680
+ for (const hlEl of getElementsByTagName(root, "hyperlink")) {
3681
+ const ref = getAttr(hlEl, "ref");
3682
+ if (!ref) continue;
3683
+ const normalized = normalizeRangeRef(ref);
3684
+ __privateGet(this, _hyperlinks).set(normalized, {
3685
+ ref: normalized,
3686
+ location: getAttr(hlEl, "location"),
3687
+ display: getAttr(hlEl, "display"),
3688
+ tooltip: getAttr(hlEl, "tooltip")
3689
+ });
3690
+ const relId = getAttr(hlEl, "r:id") ?? getAttr(hlEl, "id");
3691
+ if (relId) __privateGet(this, _pendingHyperlinkRels).set(normalized, relId);
3692
+ }
3693
+ }
3694
+ /** @internal Parses an `xl/comments*.xml` part into this sheet's comments. */
3695
+ loadCommentsXml(xml2) {
3696
+ const root = parseXml(xml2);
3697
+ const authors = getElementsByTagName(root, "author").map((a) => a.textContent);
3698
+ for (const cEl of getElementsByTagName(root, "comment")) {
3699
+ const ref = getAttr(cEl, "ref");
3700
+ if (!ref) continue;
3701
+ const authorId = parseInt(getAttr(cEl, "authorId") ?? "0", 10);
3702
+ const text = getElementsByTagName(cEl, "t").map((t) => t.textContent).join("");
3703
+ const normalized = normalizeRef(ref);
3704
+ __privateGet(this, _comments).set(normalized, {
3705
+ ref: normalized,
3706
+ text,
3707
+ author: authors[authorId] ?? ""
3708
+ });
3709
+ }
3710
+ __privateSet(this, _commentsDirty, false);
3711
+ }
3712
+ /** @internal The comments part content, when this sheet owns it. */
3713
+ buildCommentsXml() {
3714
+ const authors = [...new Set([...__privateGet(this, _comments).values()].map((c) => c.author))];
3715
+ const authorsXml = authors.map((a) => `<author>${escXml2(a)}</author>`).join("");
3716
+ const listXml = [...__privateGet(this, _comments).values()].map(
3717
+ (c) => `<comment ref="${c.ref}" authorId="${authors.indexOf(c.author)}"><text><r><t xml:space="preserve">${escXml2(c.text)}</t></r></text></comment>`
3718
+ ).join("");
3719
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3720
+ <comments xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><authors>${authorsXml}</authors><commentList>${listXml}</commentList></comments>`;
3721
+ }
3722
+ /**
3723
+ * @internal The legacy VML drawing that positions the comment boxes. Excel
3724
+ * needs it to draw the note indicator and popup; the comments part alone
3725
+ * carries only the text.
3726
+ */
3727
+ buildCommentsVml() {
3728
+ const shapes = [...__privateGet(this, _comments).values()].map((c, idx) => {
3729
+ const { row, column } = parseCellRef(c.ref);
3730
+ const anchor = `${column}, 15, ${row - 1}, 10, ${column + 2}, 15, ${row + 3}, 4`;
3731
+ 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>`;
3732
+ }).join("");
3733
+ 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>`;
2670
3734
  }
2671
3735
  };
2672
3736
  _sharedStrings2 = new WeakMap();
@@ -2680,23 +3744,263 @@ _autoFilter = new WeakMap();
2680
3744
  _dataValidations = new WeakMap();
2681
3745
  _visibility = new WeakMap();
2682
3746
  _drawings = new WeakMap();
3747
+ _protection = new WeakMap();
3748
+ _protectionCredentials = new WeakMap();
3749
+ _hyperlinks = new WeakMap();
3750
+ _comments = new WeakMap();
3751
+ _commentsDirty = new WeakMap();
3752
+ _pendingHyperlinkRels = new WeakMap();
2683
3753
  _preservedTail = new WeakMap();
2684
3754
  _preservedRels = new WeakMap();
2685
3755
  _Worksheet_instances = new WeakSet();
3756
+ /** Removes and returns a cell's data record, pruning an emptied bare row. */
3757
+ takeCellData_fn = function(ref) {
3758
+ return __privateMethod(this, _Worksheet_instances, takeCellDataAt_fn).call(this, parseCellRef(ref).row, ref);
3759
+ };
3760
+ takeCellDataAt_fn = function(row, ref) {
3761
+ const rowData = __privateGet(this, _rows).get(row);
3762
+ const data = rowData?.cells.get(ref);
3763
+ if (rowData && data) {
3764
+ rowData.cells.delete(ref);
3765
+ if (rowData.cells.size === 0 && !rowData.hidden && rowData.height === void 0) {
3766
+ __privateGet(this, _rows).delete(row);
3767
+ }
3768
+ }
3769
+ return data;
3770
+ };
3771
+ deleteCellData_fn = function(ref) {
3772
+ __privateMethod(this, _Worksheet_instances, takeCellData_fn).call(this, ref);
3773
+ };
3774
+ applyStructural_fn = function(dim, kind, at, count) {
3775
+ const what = dim === "row" ? "Row" : "Column";
3776
+ if (!Number.isInteger(at) || at < 1) throw new RangeError(`${what} index must be an integer >= 1.`);
3777
+ if (!Number.isInteger(count) || count < 1) throw new RangeError("Count must be an integer >= 1.");
3778
+ const shift = { dim, kind, at, count, sheetName: __privateGet(this, _name) };
3779
+ if (kind === "insert") {
3780
+ const limit = dim === "row" ? EXCEL_MAX_ROWS : EXCEL_MAX_COLUMNS;
3781
+ const used = dim === "row" ? __privateMethod(this, _Worksheet_instances, maxPopulatedRow_fn).call(this) : this.usedBounds.columnCount;
3782
+ if (used >= at && used + count > limit) {
3783
+ throw new RangeError(`Insert would push populated cells past the sheet limit of ${limit}.`);
3784
+ }
3785
+ }
3786
+ __privateMethod(this, _Worksheet_instances, shiftRowsAndCells_fn).call(this, shift);
3787
+ const refErrors = __privateMethod(this, _Worksheet_instances, rewriteFormulas_fn).call(this, shift);
3788
+ __privateMethod(this, _Worksheet_instances, shiftMerges_fn).call(this, shift);
3789
+ __privateMethod(this, _Worksheet_instances, shiftAutoFilter_fn).call(this, shift);
3790
+ __privateMethod(this, _Worksheet_instances, shiftValidations_fn).call(this, shift);
3791
+ __privateMethod(this, _Worksheet_instances, shiftHyperlinks_fn).call(this, shift);
3792
+ __privateMethod(this, _Worksheet_instances, shiftComments_fn).call(this, shift);
3793
+ if (dim === "col") __privateMethod(this, _Worksheet_instances, shiftColDefs_fn).call(this, shift);
3794
+ return { refErrors };
3795
+ };
3796
+ /**
3797
+ * Re-keys the row map / per-row cell maps and shifts every cell's spill range.
3798
+ *
3799
+ * Rows and columns before the shift boundary keep their keys, their reference
3800
+ * strings and their identity, so an edit near the end of a sheet costs
3801
+ * proportionally little rather than rebuilding the whole model. Spill ranges
3802
+ * are the one thing that must be visited everywhere, since a range belonging
3803
+ * to an unmoved anchor can still span the boundary — but that pass is a bare
3804
+ * property test per cell.
3805
+ */
3806
+ shiftRowsAndCells_fn = function(shift) {
3807
+ if (shift.dim === "row") __privateMethod(this, _Worksheet_instances, shiftRows_fn).call(this, shift);
3808
+ else __privateMethod(this, _Worksheet_instances, shiftColumns_fn).call(this, shift);
3809
+ };
3810
+ shiftRows_fn = function(shift) {
3811
+ const moved = [];
3812
+ for (const row of __privateGet(this, _rows).values()) {
3813
+ if (row.rowIndex < shift.at) {
3814
+ __privateMethod(this, _Worksheet_instances, shiftSpillRanges_fn).call(this, row, shift);
3815
+ continue;
3816
+ }
3817
+ const shifted = shiftIndex(row.rowIndex, shift);
3818
+ __privateGet(this, _rows).delete(row.rowIndex);
3819
+ if (shifted === null) continue;
3820
+ row.rowIndex = shifted;
3821
+ moved.push(row);
3822
+ }
3823
+ for (const row of moved) {
3824
+ const suffix = String(row.rowIndex);
3825
+ const newCells = /* @__PURE__ */ new Map();
3826
+ for (const cd of row.cells.values()) {
3827
+ const ref = cd.reference.slice(0, refSplit(cd.reference)) + suffix;
3828
+ cd.reference = ref;
3829
+ if (cd.arrayRef) cd.arrayRef = shiftRangeRef(cd.arrayRef, shift) ?? void 0;
3830
+ newCells.set(ref, cd);
3831
+ }
3832
+ row.cells = newCells;
3833
+ __privateGet(this, _rows).set(row.rowIndex, row);
3834
+ }
3835
+ };
3836
+ shiftColumns_fn = function(shift) {
3837
+ for (const row of __privateGet(this, _rows).values()) {
3838
+ let affected;
3839
+ for (const cd of row.cells.values()) {
3840
+ const ref = cd.reference;
3841
+ if (columnFromRef(ref, refSplit(ref)) < shift.at) {
3842
+ __privateMethod(this, _Worksheet_instances, shiftSpillRange_fn).call(this, cd, shift);
3843
+ continue;
3844
+ }
3845
+ (affected ?? (affected = [])).push(cd);
3846
+ }
3847
+ if (!affected) continue;
3848
+ for (const cd of affected) row.cells.delete(cd.reference);
3849
+ for (const cd of affected) {
3850
+ const split = refSplit(cd.reference);
3851
+ const shifted = shiftIndex(columnFromRef(cd.reference, split), shift);
3852
+ if (shifted === null) continue;
3853
+ const ref = columnLetter(shifted) + cd.reference.slice(split);
3854
+ cd.reference = ref;
3855
+ __privateMethod(this, _Worksheet_instances, shiftSpillRange_fn).call(this, cd, shift);
3856
+ row.cells.set(ref, cd);
3857
+ }
3858
+ }
3859
+ };
3860
+ /** Largest 1-based row index holding a cell, or 0. O(rows), no ref parsing. */
3861
+ maxPopulatedRow_fn = function() {
3862
+ let max = 0;
3863
+ for (const row of __privateGet(this, _rows).values()) {
3864
+ if (row.cells.size > 0 && row.rowIndex > max) max = row.rowIndex;
3865
+ }
3866
+ return max;
3867
+ };
3868
+ shiftSpillRanges_fn = function(row, shift) {
3869
+ for (const cd of row.cells.values()) __privateMethod(this, _Worksheet_instances, shiftSpillRange_fn).call(this, cd, shift);
3870
+ };
3871
+ shiftSpillRange_fn = function(cd, shift) {
3872
+ if (cd.arrayRef) cd.arrayRef = shiftRangeRef(cd.arrayRef, shift) ?? void 0;
3873
+ };
3874
+ /** Rewrites every formula on the sheet; returns refs that now hold #REF!. */
3875
+ rewriteFormulas_fn = function(shift) {
3876
+ const refErrors = [];
3877
+ for (const row of __privateGet(this, _rows).values()) {
3878
+ for (const cd of row.cells.values()) {
3879
+ if (!cd.formula) continue;
3880
+ const result = shiftFormulaRefs(cd.formula, shift, __privateGet(this, _name));
3881
+ if (result.changed) cd.formula = result.text;
3882
+ if (result.hasRefError) refErrors.push(cd.reference);
3883
+ }
3884
+ }
3885
+ return refErrors;
3886
+ };
3887
+ shiftMerges_fn = function(shift) {
3888
+ __privateSet(this, _mergeCells, __privateGet(this, _mergeCells).flatMap((m) => {
3889
+ const ref = shiftRangeRef(m.ref, shift);
3890
+ if (!ref) return [];
3891
+ const [a, b] = ref.split(":");
3892
+ if (!b || a === b) return [];
3893
+ return [{ ref }];
3894
+ }));
3895
+ };
3896
+ shiftAutoFilter_fn = function(shift) {
3897
+ if (!__privateGet(this, _autoFilter)) return;
3898
+ const ref = shiftRangeRef(__privateGet(this, _autoFilter).ref, shift);
3899
+ __privateSet(this, _autoFilter, ref ? { ref } : void 0);
3900
+ };
3901
+ shiftValidations_fn = function(shift) {
3902
+ __privateSet(this, _dataValidations, __privateGet(this, _dataValidations).flatMap((dv) => {
3903
+ const sqref = shiftSqref(dv.sqref, shift);
3904
+ if (!sqref) return [];
3905
+ const formula = shiftFormulaRefs(dv.formula, shift, __privateGet(this, _name)).text;
3906
+ return [{ sqref, formula }];
3907
+ }));
3908
+ };
3909
+ shiftHyperlinks_fn = function(shift) {
3910
+ const moved = /* @__PURE__ */ new Map();
3911
+ for (const link of __privateGet(this, _hyperlinks).values()) {
3912
+ const ref = shiftRangeRef(link.ref, shift);
3913
+ if (!ref) continue;
3914
+ moved.set(ref, { ...link, ref });
3915
+ }
3916
+ __privateSet(this, _hyperlinks, moved);
3917
+ };
3918
+ shiftComments_fn = function(shift) {
3919
+ const moved = /* @__PURE__ */ new Map();
3920
+ let changed = false;
3921
+ for (const comment of __privateGet(this, _comments).values()) {
3922
+ const ref = shiftRangeRef(comment.ref, shift);
3923
+ if (!ref) {
3924
+ changed = true;
3925
+ continue;
3926
+ }
3927
+ if (ref !== comment.ref) changed = true;
3928
+ moved.set(ref, { ...comment, ref });
3929
+ }
3930
+ __privateSet(this, _comments, moved);
3931
+ if (changed) __privateSet(this, _commentsDirty, true);
3932
+ };
3933
+ shiftColDefs_fn = function(shift) {
3934
+ __privateSet(this, _cols, __privateGet(this, _cols).flatMap((c) => {
3935
+ const span = shiftSpan(c.min, c.max, shift);
3936
+ return span ? [{ ...c, min: span.start, max: span.end }] : [];
3937
+ }));
3938
+ };
3939
+ /** Highest numeric preserved relationship id, so generated ids clear them all. */
3940
+ relIdBase_fn = function() {
3941
+ let max = 0;
3942
+ for (const r of __privateGet(this, _preservedRels)) {
3943
+ const n = RID_PATTERN.exec(r.id);
3944
+ if (n) max = Math.max(max, parseInt(n[1], 10));
3945
+ }
3946
+ return max;
3947
+ };
2686
3948
  /**
2687
3949
  * Preserved leaf elements that will actually be emitted. A sheet may only have
2688
3950
  * one `<drawing>`, so live providers suppress a preserved one — every consumer
2689
3951
  * (serialisation, relationships, pruning) must agree on that, hence one helper.
2690
3952
  */
2691
3953
  activeTail_fn = function() {
2692
- return __privateGet(this, _preservedTail).filter((t) => !(t.tag === "drawing" && this.hasDrawings));
3954
+ return __privateGet(this, _preservedTail).filter((t) => {
3955
+ if (t.tag === "drawing") return !this.hasDrawings;
3956
+ if (t.tag === "legacyDrawing") return !this.commentPartsReplaced;
3957
+ return true;
3958
+ });
3959
+ };
3960
+ /**
3961
+ * The relationship-bearing leaf elements, in schema order: `drawing`,
3962
+ * `legacyDrawing`, `legacyDrawingHF`, `picture`. A sheet may hold only one of
3963
+ * each, so generated content replaces the preserved element of the same tag —
3964
+ * live drawing providers replace a preserved `<drawing>`, and comments this
3965
+ * sheet now owns replace a preserved `<legacyDrawing>`.
3966
+ */
3967
+ buildTailXml_fn = function(rels) {
3968
+ const preserved = __privateMethod(this, _Worksheet_instances, activeTail_fn).call(this);
3969
+ const byTag = (tag) => preserved.filter((t) => t.tag === tag).map((t) => t.xml).join("");
3970
+ const drawing = rels.drawing ? `<drawing r:id="${rels.drawing}"/>` : byTag("drawing");
3971
+ const legacy = rels.vmlDrawing ? `<legacyDrawing r:id="${rels.vmlDrawing}"/>` : byTag("legacyDrawing");
3972
+ return drawing + legacy + byTag("legacyDrawingHF") + byTag("picture");
3973
+ };
3974
+ buildSheetProtectionXml_fn = function() {
3975
+ if (!__privateGet(this, _protection)) return "";
3976
+ let attrs = ' sheet="1"';
3977
+ for (const [key, attr] of PROTECTION_ATTRS) {
3978
+ const blocked = !__privateGet(this, _protection)[key];
3979
+ const defaultBlocked = !PROTECTION_DEFAULTS[key];
3980
+ if (blocked !== defaultBlocked) attrs += ` ${attr}="${blocked ? 1 : 0}"`;
3981
+ }
3982
+ return `<sheetProtection${__privateGet(this, _protectionCredentials)}${attrs}/>`;
3983
+ };
3984
+ buildHyperlinksXml_fn = function(relIds) {
3985
+ if (__privateGet(this, _hyperlinks).size === 0) return "";
3986
+ const inner = [...__privateGet(this, _hyperlinks).values()].map((link) => {
3987
+ const relId = relIds.get(link.ref);
3988
+ const parts = [`ref="${link.ref}"`];
3989
+ if (relId) parts.push(`r:id="${relId}"`);
3990
+ if (link.location) parts.push(`location="${escXmlAttr(link.location)}"`);
3991
+ if (link.display) parts.push(`display="${escXmlAttr(link.display)}"`);
3992
+ if (link.tooltip) parts.push(`tooltip="${escXmlAttr(link.tooltip)}"`);
3993
+ return `<hyperlink ${parts.join(" ")}/>`;
3994
+ }).join("");
3995
+ return `<hyperlinks>${inner}</hyperlinks>`;
2693
3996
  };
2694
3997
  // ── Private XML builders ───────────────────────────────────────────────────
2695
3998
  buildColsXml_fn = function() {
2696
3999
  if (__privateGet(this, _cols).length === 0) return "";
2697
4000
  const inner = __privateGet(this, _cols).map((c) => {
4001
+ const width = c.width !== void 0 ? ` width="${c.width.toFixed(2)}" customWidth="${c.customWidth ? 1 : 0}"` : "";
2698
4002
  const hidden = c.hidden ? ' hidden="1"' : "";
2699
- return `<col min="${c.min}" max="${c.max}" width="${c.width.toFixed(2)}" customWidth="${c.customWidth ? 1 : 0}"${hidden}/>`;
4003
+ return `<col min="${c.min}" max="${c.max}"${width}${hidden}/>`;
2700
4004
  }).join("");
2701
4005
  return `<cols>${inner}</cols>`;
2702
4006
  };
@@ -2706,12 +4010,8 @@ buildSheetDataXml_fn = function() {
2706
4010
  const rowsXml = sortedRows.map((row) => {
2707
4011
  const ht = row.height !== void 0 && row.customHeight ? ` ht="${row.height}" customHeight="1"` : "";
2708
4012
  const hidden = row.hidden ? ' hidden="1"' : "";
2709
- const sortedCells = [...row.cells.values()].sort((a, b) => {
2710
- const ra = parseCellRef(a.reference);
2711
- const rb = parseCellRef(b.reference);
2712
- return ra.column - rb.column;
2713
- });
2714
- const cellsXml = sortedCells.map((cd) => new Cell(cd, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2)).toXml()).filter(Boolean).join("");
4013
+ const sortedCells = [...row.cells.values()].map((cd) => ({ cd, col: columnFromRef(cd.reference, refSplit(cd.reference)) })).sort((a, b) => a.col - b.col);
4014
+ const cellsXml = sortedCells.map(({ cd }) => new Cell(cd, __privateGet(this, _sharedStrings2), __privateGet(this, _styles2)).toXml()).filter(Boolean).join("");
2715
4015
  if (!cellsXml && !ht && !hidden) return "";
2716
4016
  return `<row r="${row.rowIndex}"${ht}${hidden}>${cellsXml}</row>`;
2717
4017
  }).filter(Boolean).join("");
@@ -2740,6 +4040,61 @@ buildSheetViewXml_fn = function() {
2740
4040
  const ySplit = row > 0 ? ` ySplit="${row}"` : "";
2741
4041
  return `<sheetView workbookViewId="0"><pane${xSplit}${ySplit} topLeftCell="${topLeft}" activePane="${activePane}" state="frozen"/></sheetView>`;
2742
4042
  };
4043
+ /**
4044
+ * Returns the single-column ColDef covering `columnIndex`, splitting a wider
4045
+ * span into up to three pieces so the other columns keep their width/hidden
4046
+ * state. Returns undefined when no definition covers the column.
4047
+ */
4048
+ splitColSpan_fn = function(columnIndex) {
4049
+ const idx = __privateMethod(this, _Worksheet_instances, findColDefIndex_fn).call(this, columnIndex);
4050
+ if (idx === -1) return void 0;
4051
+ const span = __privateGet(this, _cols)[idx];
4052
+ if (span.min === span.max) return span;
4053
+ const pieces = [];
4054
+ if (span.min < columnIndex) pieces.push({ ...span, max: columnIndex - 1 });
4055
+ const target = { ...span, min: columnIndex, max: columnIndex };
4056
+ pieces.push(target);
4057
+ if (span.max > columnIndex) pieces.push({ ...span, min: columnIndex + 1 });
4058
+ __privateGet(this, _cols).splice(idx, 1, ...pieces);
4059
+ return target;
4060
+ };
4061
+ /**
4062
+ * Inserts a definition keeping `#cols` sorted by `min`. Binary insertion,
4063
+ * because re-sorting the whole array per call makes setting widths for every
4064
+ * column of a wide sheet quadratic.
4065
+ */
4066
+ insertColDef_fn = function(def) {
4067
+ let lo = 0;
4068
+ let hi = __privateGet(this, _cols).length;
4069
+ while (lo < hi) {
4070
+ const mid = lo + hi >>> 1;
4071
+ if (__privateGet(this, _cols)[mid].min < def.min) lo = mid + 1;
4072
+ else hi = mid;
4073
+ }
4074
+ __privateGet(this, _cols).splice(lo, 0, def);
4075
+ return def;
4076
+ };
4077
+ /** Index of the definition covering `columnIndex`, or -1. Binary search. */
4078
+ findColDefIndex_fn = function(columnIndex) {
4079
+ let lo = 0;
4080
+ let hi = __privateGet(this, _cols).length - 1;
4081
+ while (lo <= hi) {
4082
+ const mid = lo + hi >>> 1;
4083
+ const c = __privateGet(this, _cols)[mid];
4084
+ if (columnIndex < c.min) hi = mid - 1;
4085
+ else if (columnIndex > c.max) lo = mid + 1;
4086
+ else return mid;
4087
+ }
4088
+ return -1;
4089
+ };
4090
+ findColDef_fn = function(columnIndex) {
4091
+ const idx = __privateMethod(this, _Worksheet_instances, findColDefIndex_fn).call(this, columnIndex);
4092
+ return idx === -1 ? void 0 : __privateGet(this, _cols)[idx];
4093
+ };
4094
+ /** Drops definitions that no longer carry any information. */
4095
+ pruneColDefs_fn = function() {
4096
+ __privateSet(this, _cols, __privateGet(this, _cols).filter((c) => c.width !== void 0 || c.customWidth || c.hidden));
4097
+ };
2743
4098
  getOrCreateRow_fn = function(rowIndex) {
2744
4099
  let row = __privateGet(this, _rows).get(rowIndex);
2745
4100
  if (!row) {
@@ -2748,9 +4103,64 @@ getOrCreateRow_fn = function(rowIndex) {
2748
4103
  }
2749
4104
  return row;
2750
4105
  };
4106
+ var RID_PATTERN = /^rId(\d+)$/;
2751
4107
  function escXml2(s) {
2752
4108
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2753
4109
  }
4110
+ function escXmlAttr(s) {
4111
+ return escXml2(s).replace(/"/g, "&quot;");
4112
+ }
4113
+ function normalizeRef(ref) {
4114
+ const normalized = normalizeRefFast(ref);
4115
+ parseCellRef(normalized);
4116
+ return normalized;
4117
+ }
4118
+ function normalizeRangeRef(ref) {
4119
+ const normalized = normalizeRefFast(ref);
4120
+ const colon = normalized.indexOf(":");
4121
+ if (colon === -1) {
4122
+ parseCellRef(normalized);
4123
+ return normalized;
4124
+ }
4125
+ parseCellRef(normalized.slice(0, colon));
4126
+ parseCellRef(normalized.slice(colon + 1));
4127
+ return normalized;
4128
+ }
4129
+ function normalizeRefFast(ref) {
4130
+ return ref.indexOf("$") === -1 ? upperFast(ref) : ref.replace(/\$/g, "").toUpperCase();
4131
+ }
4132
+ function upperFast(s) {
4133
+ for (let i = 0; i < s.length; i++) {
4134
+ const code = s.charCodeAt(i);
4135
+ if (code >= 97 && code <= 122) return s.toUpperCase();
4136
+ }
4137
+ return s;
4138
+ }
4139
+ function refSplit(ref) {
4140
+ let i = 0;
4141
+ while (i < ref.length) {
4142
+ const code = ref.charCodeAt(i);
4143
+ if (code < 65 || code > 90) break;
4144
+ i++;
4145
+ }
4146
+ return i;
4147
+ }
4148
+ function columnFromRef(ref, split) {
4149
+ let col = 0;
4150
+ for (let i = 0; i < split; i++) col = col * 26 + (ref.charCodeAt(i) - 64);
4151
+ return col;
4152
+ }
4153
+ function translateRangeRef(ref, deltaRow, deltaCol) {
4154
+ const parts = ref.split(":").map((p) => {
4155
+ const { row, column } = parseCellRef(p);
4156
+ const r = row + deltaRow;
4157
+ const c = column + deltaCol;
4158
+ if (r < 1 || c < 1 || r > EXCEL_MAX_ROWS || c > EXCEL_MAX_COLUMNS) return null;
4159
+ return cellRefFromRowCol(r, c);
4160
+ });
4161
+ if (parts.some((p) => p === null)) return void 0;
4162
+ return parts.join(":");
4163
+ }
2754
4164
 
2755
4165
  // src/model/defined-name.ts
2756
4166
  var _names, _DefinedNameManager_instances, indexOf_fn;
@@ -2815,7 +4225,7 @@ indexOf_fn = function(name, scope) {
2815
4225
  };
2816
4226
 
2817
4227
  // src/model/workbook.ts
2818
- var _sharedStrings3, _styles3, _sheets, _definedNames, _closed, _preservedParts, _preservedContentTypes, _orphanedTargets, _Workbook_instances, assertOpen_fn, seedPreservedParts_fn, preservedContentTypeDeclarations_fn, buildZipEntries_fn;
4228
+ var _sharedStrings3, _styles3, _sheets, _definedNames, _closed, _preservedParts, _preservedContentTypes, _orphanedTargets, _Workbook_instances, structuralEdit_fn, assertOpen_fn, seedPreservedParts_fn, preservedContentTypeDeclarations_fn, buildZipEntries_fn;
2819
4229
  var _Workbook = class _Workbook {
2820
4230
  constructor(sharedStrings, styles) {
2821
4231
  __privateAdd(this, _Workbook_instances);
@@ -2875,6 +4285,11 @@ var _Workbook = class _Workbook {
2875
4285
  const wsRelsXml = readXmlEntry(entries, `${dir}/_rels/${file}.rels`);
2876
4286
  if (wsRelsXml) {
2877
4287
  ws.loadPreservedState(wsXml, parseRelationships(wsRelsXml, wsPath));
4288
+ const commentsPath = ws.commentsPartPath();
4289
+ if (commentsPath) {
4290
+ const commentsXml = readXmlEntry(entries, commentsPath);
4291
+ if (commentsXml) ws.loadCommentsXml(commentsXml);
4292
+ }
2878
4293
  }
2879
4294
  }
2880
4295
  __privateGet(wb, _sheets).push({ worksheet: ws, sheetId, relId });
@@ -2963,6 +4378,32 @@ var _Workbook = class _Workbook {
2963
4378
  if (!entry) throw new Error(`Worksheet "${name}" not found.`);
2964
4379
  return entry.worksheet;
2965
4380
  }
4381
+ // ── Structural editing (workbook-wide) ───────────────────────────────────────
4382
+ /**
4383
+ * Inserts rows on one sheet and keeps the whole workbook consistent:
4384
+ * formulas on OTHER sheets that reference the edited sheet, and defined
4385
+ * names, are rewritten too. Prefer this over `Worksheet.insertRows` in a
4386
+ * multi-sheet workbook.
4387
+ *
4388
+ * @returns The edited sheet's result; `refErrors` additionally includes
4389
+ * sheet-qualified refs (e.g. `"Other!B2"`) for affected formula
4390
+ * cells on other sheets.
4391
+ */
4392
+ insertRows(sheetName, at, count = 1) {
4393
+ return __privateMethod(this, _Workbook_instances, structuralEdit_fn).call(this, sheetName, "row", "insert", at, count);
4394
+ }
4395
+ /** Workbook-wide counterpart to `Worksheet.deleteRows`. See {@link insertRows}. */
4396
+ deleteRows(sheetName, at, count = 1) {
4397
+ return __privateMethod(this, _Workbook_instances, structuralEdit_fn).call(this, sheetName, "row", "delete", at, count);
4398
+ }
4399
+ /** Workbook-wide counterpart to `Worksheet.insertColumns`. See {@link insertRows}. */
4400
+ insertColumns(sheetName, at, count = 1) {
4401
+ return __privateMethod(this, _Workbook_instances, structuralEdit_fn).call(this, sheetName, "col", "insert", at, count);
4402
+ }
4403
+ /** Workbook-wide counterpart to `Worksheet.deleteColumns`. See {@link insertRows}. */
4404
+ deleteColumns(sheetName, at, count = 1) {
4405
+ return __privateMethod(this, _Workbook_instances, structuralEdit_fn).call(this, sheetName, "col", "delete", at, count);
4406
+ }
2966
4407
  // ── Defined names (named ranges) ─────────────────────────────────────────────
2967
4408
  /**
2968
4409
  * Defines (or replaces) a named range.
@@ -3043,6 +4484,23 @@ _preservedParts = new WeakMap();
3043
4484
  _preservedContentTypes = new WeakMap();
3044
4485
  _orphanedTargets = new WeakMap();
3045
4486
  _Workbook_instances = new WeakSet();
4487
+ structuralEdit_fn = function(sheetName, dim, kind, at, count) {
4488
+ __privateMethod(this, _Workbook_instances, assertOpen_fn).call(this);
4489
+ const target = this.getWorksheet(sheetName);
4490
+ const result = dim === "row" ? kind === "insert" ? target.insertRows(at, count) : target.deleteRows(at, count) : kind === "insert" ? target.insertColumns(at, count) : target.deleteColumns(at, count);
4491
+ const shift = { dim, kind, at, count, sheetName: target.name };
4492
+ const refErrors = [...result.refErrors];
4493
+ for (const ws of this.worksheets) {
4494
+ if (ws === target) continue;
4495
+ const external = ws.applyExternalShift(shift);
4496
+ for (const ref of external.refErrors) refErrors.push(`${ws.name}!${ref}`);
4497
+ }
4498
+ for (const dn of [...__privateGet(this, _definedNames).all]) {
4499
+ const rewritten = shiftFormulaRefs(dn.refersTo, shift, dn.scope ?? "");
4500
+ if (rewritten.changed) __privateGet(this, _definedNames).define(dn.name, rewritten.text, dn.scope);
4501
+ }
4502
+ return { refErrors };
4503
+ };
3046
4504
  // ── Private helpers ────────────────────────────────────────────────────────
3047
4505
  assertOpen_fn = function() {
3048
4506
  if (__privateGet(this, _closed)) throw new Error("Workbook has been closed.");
@@ -3123,13 +4581,40 @@ buildZipEntries_fn = function() {
3123
4581
  entries.set(`xl/worksheets/sheet${i + 1}.xml`, __privateGet(this, _sheets)[i].worksheet.buildXml());
3124
4582
  }
3125
4583
  const extraOverrides = [];
4584
+ const extraDefaults = [];
3126
4585
  let drawingCounter = 0;
3127
4586
  let partCounter = 0;
4587
+ let commentsCounter = 0;
3128
4588
  for (let i = 0; i < __privateGet(this, _sheets).length; i++) {
3129
4589
  const ws = __privateGet(this, _sheets)[i].worksheet;
3130
4590
  const sheetRels = ws.preservedRelationships.map(
3131
- (r) => ` <Relationship Id="${r.id}" Type="${r.type}" Target="${r.target}"${r.external ? ' TargetMode="External"' : ""}/>`
4591
+ (r) => ` <Relationship Id="${r.id}" Type="${r.type}" Target="${escapeXml(r.target)}"${r.external ? ' TargetMode="External"' : ""}/>`
3132
4592
  );
4593
+ const generated = ws.generatedRels();
4594
+ for (const link of ws.hyperlinks) {
4595
+ const relId = generated.hyperlinks.get(link.ref);
4596
+ if (!relId || !link.target) continue;
4597
+ sheetRels.push(
4598
+ ` <Relationship Id="${relId}" Type="${REL_TYPES.hyperlink}" Target="${escapeXml(link.target)}" TargetMode="External"/>`
4599
+ );
4600
+ }
4601
+ if (generated.comments && generated.vmlDrawing) {
4602
+ do {
4603
+ commentsCounter++;
4604
+ } while (preservedPaths.has(`xl/comments${commentsCounter}.xml`) || preservedPaths.has(`xl/drawings/vmlDrawing${commentsCounter}.vml`));
4605
+ const commentsPath = `xl/comments${commentsCounter}.xml`;
4606
+ const vmlPath = `xl/drawings/vmlDrawing${commentsCounter}.vml`;
4607
+ entries.set(commentsPath, ws.buildCommentsXml());
4608
+ entries.set(vmlPath, ws.buildCommentsVml());
4609
+ extraOverrides.push({ partName: `/${commentsPath}`, contentType: CONTENT_TYPES.comments });
4610
+ if (!extraDefaults.some((d) => d.extension === "vml")) {
4611
+ extraDefaults.push({ extension: "vml", contentType: CONTENT_TYPES.vmlDrawing });
4612
+ }
4613
+ sheetRels.push(
4614
+ ` <Relationship Id="${generated.comments}" Type="${REL_TYPES.comments}" Target="../comments${commentsCounter}.xml"/>`,
4615
+ ` <Relationship Id="${generated.vmlDrawing}" Type="${REL_TYPES.vmlDrawing}" Target="../drawings/vmlDrawing${commentsCounter}.vml"/>`
4616
+ );
4617
+ }
3133
4618
  if (ws.hasDrawings) {
3134
4619
  do {
3135
4620
  drawingCounter++;
@@ -3166,7 +4651,7 @@ buildZipEntries_fn = function() {
3166
4651
  buildContentTypes(
3167
4652
  sheetCount,
3168
4653
  [...preservedCt.overrides, ...extraOverrides],
3169
- preservedCt.defaults
4654
+ [...preservedCt.defaults, ...extraDefaults]
3170
4655
  )
3171
4656
  );
3172
4657
  return entries;
@@ -3191,6 +4676,8 @@ exports.CONTENT_TYPES = CONTENT_TYPES;
3191
4676
  exports.Cell = Cell;
3192
4677
  exports.CellStyle = CellStyle;
3193
4678
  exports.DefinedNameManager = DefinedNameManager;
4679
+ exports.EXCEL_MAX_COLUMNS = EXCEL_MAX_COLUMNS;
4680
+ exports.EXCEL_MAX_ROWS = EXCEL_MAX_ROWS;
3194
4681
  exports.ExcelAlignment = ExcelAlignment;
3195
4682
  exports.ExcelBorder = ExcelBorder;
3196
4683
  exports.ExcelBorderEdge = ExcelBorderEdge;
@@ -3219,9 +4706,12 @@ exports.cellRefFromRowCol = cellRefFromRowCol;
3219
4706
  exports.columnLetter = columnLetter;
3220
4707
  exports.columnNumber = columnNumber;
3221
4708
  exports.decodeUtf8 = decodeUtf8;
4709
+ exports.decodeXmlEntities = decodeXmlEntities;
3222
4710
  exports.encodeUtf8 = encodeUtf8;
3223
4711
  exports.escapeXml = escapeXml;
4712
+ exports.formatDateCode = formatDateCode;
3224
4713
  exports.formatNumberWithCode = formatNumberWithCode;
4714
+ exports.formatRangeRef = formatRangeRef;
3225
4715
  exports.fromOADate = fromOADate;
3226
4716
  exports.getAttr = getAttr;
3227
4717
  exports.getElementsByTagName = getElementsByTagName;
@@ -3235,6 +4725,11 @@ exports.readXmlEntry = readXmlEntry;
3235
4725
  exports.requireXmlEntry = requireXmlEntry;
3236
4726
  exports.setBinaryEntry = setBinaryEntry;
3237
4727
  exports.setXmlEntry = setXmlEntry;
4728
+ exports.shiftFormulaRefs = shiftFormulaRefs;
4729
+ exports.shiftIndex = shiftIndex;
4730
+ exports.shiftRangeRef = shiftRangeRef;
4731
+ exports.shiftSpan = shiftSpan;
4732
+ exports.shiftSqref = shiftSqref;
3238
4733
  exports.toOADate = toOADate;
3239
4734
  exports.unzipXlsx = unzipXlsx;
3240
4735
  exports.xml = xml;