@cj-tech-master/excelts 5.1.11 → 5.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/browser/modules/excel/stream/worksheet-writer.js +13 -5
  2. package/dist/browser/modules/excel/utils/cell-format.js +4 -37
  3. package/dist/browser/modules/excel/utils/merge-borders.d.ts +44 -0
  4. package/dist/browser/modules/excel/utils/merge-borders.js +105 -0
  5. package/dist/browser/modules/excel/worksheet.js +15 -5
  6. package/dist/browser/utils/utils.base.d.ts +8 -0
  7. package/dist/browser/utils/utils.base.js +51 -7
  8. package/dist/browser/utils/utils.browser.d.ts +1 -1
  9. package/dist/browser/utils/utils.browser.js +1 -1
  10. package/dist/browser/utils/utils.d.ts +1 -1
  11. package/dist/browser/utils/utils.js +1 -1
  12. package/dist/cjs/modules/excel/stream/worksheet-writer.js +13 -5
  13. package/dist/cjs/modules/excel/utils/cell-format.js +3 -36
  14. package/dist/cjs/modules/excel/utils/merge-borders.js +109 -0
  15. package/dist/cjs/modules/excel/worksheet.js +15 -5
  16. package/dist/cjs/utils/utils.base.js +52 -7
  17. package/dist/cjs/utils/utils.browser.js +2 -1
  18. package/dist/cjs/utils/utils.js +2 -1
  19. package/dist/esm/modules/excel/stream/worksheet-writer.js +13 -5
  20. package/dist/esm/modules/excel/utils/cell-format.js +4 -37
  21. package/dist/esm/modules/excel/utils/merge-borders.js +105 -0
  22. package/dist/esm/modules/excel/worksheet.js +15 -5
  23. package/dist/esm/utils/utils.base.js +51 -7
  24. package/dist/esm/utils/utils.browser.js +1 -1
  25. package/dist/esm/utils/utils.js +1 -1
  26. package/dist/iife/excelts.iife.js +140 -38
  27. package/dist/iife/excelts.iife.js.map +1 -1
  28. package/dist/iife/excelts.iife.min.js +23 -23
  29. package/dist/types/modules/excel/utils/merge-borders.d.ts +44 -0
  30. package/dist/types/utils/utils.base.d.ts +8 -0
  31. package/dist/types/utils/utils.browser.d.ts +1 -1
  32. package/dist/types/utils/utils.d.ts +1 -1
  33. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @cj-tech-master/excelts v5.1.11
2
+ * @cj-tech-master/excelts v5.1.12
3
3
  * TypeScript Excel Workbook Manager - Read and Write xlsx and csv Files.
4
4
  * (c) 2026 cjnoname
5
5
  * Released under the MIT License
@@ -3110,12 +3110,46 @@ var ExcelTS = (function(exports) {
3110
3110
  const i = typeof value === "number" ? value : parseInt(value, 10);
3111
3111
  return Number.isNaN(i) ? 0 : i;
3112
3112
  }
3113
+ /**
3114
+ * Split an Excel numFmt string by semicolons, respecting quoted strings and brackets.
3115
+ *
3116
+ * Excel numFmt can have up to 4 sections: `positive ; negative ; zero ; text`.
3117
+ * Semicolons inside `"..."` (literal text) or `[...]` (locale/color tags) must NOT
3118
+ * be treated as section separators.
3119
+ */
3120
+ function splitFormatSections(fmt) {
3121
+ const sections = [];
3122
+ let current = "";
3123
+ let inQuote = false;
3124
+ let inBracket = false;
3125
+ for (let i = 0; i < fmt.length; i++) {
3126
+ const char = fmt[i];
3127
+ if (char === "\"" && !inBracket) {
3128
+ inQuote = !inQuote;
3129
+ current += char;
3130
+ } else if (char === "[" && !inQuote) {
3131
+ inBracket = true;
3132
+ current += char;
3133
+ } else if (char === "]" && !inQuote) {
3134
+ inBracket = false;
3135
+ current += char;
3136
+ } else if (char === ";" && !inQuote && !inBracket) {
3137
+ sections.push(current);
3138
+ current = "";
3139
+ } else current += char;
3140
+ }
3141
+ sections.push(current);
3142
+ return sections;
3143
+ }
3144
+ /** Reusable regex — no capture groups, so safe for `test()`. */
3145
+ const DATE_FMT_RE = /[ymdhMsb]/;
3146
+ /** Strips bracket expressions `[...]` and quoted literals `"..."` from a format string. */
3147
+ const STRIP_BRACKETS_QUOTES_RE = /\[[^\]]*\]|"[^"]*"/g;
3113
3148
  function isDateFmt(fmt) {
3114
3149
  if (!fmt) return false;
3115
- if (fmt.indexOf("@") > -1) return false;
3116
- let cleanFmt = fmt.replace(/\[[^\]]*\]/g, "");
3117
- cleanFmt = cleanFmt.replace(/"[^"]*"/g, "");
3118
- return cleanFmt.match(/[ymdhMsb]+/) !== null;
3150
+ const clean = splitFormatSections(fmt)[0].replace(STRIP_BRACKETS_QUOTES_RE, "");
3151
+ if (clean.indexOf("@") > -1) return false;
3152
+ return DATE_FMT_RE.test(clean);
3119
3153
  }
3120
3154
  function parseBoolean(value) {
3121
3155
  return value === true || value === "true" || value === 1 || value === "1";
@@ -5757,6 +5791,95 @@ var ExcelTS = (function(exports) {
5757
5791
  return result;
5758
5792
  }
5759
5793
 
5794
+ //#endregion
5795
+ //#region src/modules/excel/utils/merge-borders.ts
5796
+ /**
5797
+ * Collect perimeter borders from cells before a merge is applied.
5798
+ * Must be called BEFORE cell.merge() overwrites slave styles.
5799
+ *
5800
+ * Only iterates the four edges of the range, not the full rectangle.
5801
+ * For perimeter edges where the cell has no border, falls back to the master's border.
5802
+ */
5803
+ function collectMergeBorders(top, left, bottom, right, findCell) {
5804
+ const masterBorder = findCell(top, left)?.style?.border;
5805
+ const width = right - left + 1;
5806
+ const height = bottom - top + 1;
5807
+ const topEdges = new Array(width);
5808
+ const bottomEdges = new Array(width);
5809
+ const leftEdges = new Array(height);
5810
+ const rightEdges = new Array(height);
5811
+ let hasAny = false;
5812
+ for (let j = left; j <= right; j++) {
5813
+ const idx = j - left;
5814
+ const topBorder = findCell(top, j)?.style?.border;
5815
+ topEdges[idx] = topBorder?.top || masterBorder?.top;
5816
+ if (bottom !== top) bottomEdges[idx] = (findCell(bottom, j)?.style?.border)?.bottom || masterBorder?.bottom;
5817
+ else bottomEdges[idx] = topBorder?.bottom || masterBorder?.bottom;
5818
+ if (topEdges[idx] || bottomEdges[idx]) hasAny = true;
5819
+ }
5820
+ for (let i = top; i <= bottom; i++) {
5821
+ const idx = i - top;
5822
+ const leftBorder = findCell(i, left)?.style?.border;
5823
+ leftEdges[idx] = leftBorder?.left || masterBorder?.left;
5824
+ if (right !== left) rightEdges[idx] = (findCell(i, right)?.style?.border)?.right || masterBorder?.right;
5825
+ else rightEdges[idx] = leftBorder?.right || masterBorder?.right;
5826
+ if (leftEdges[idx] || rightEdges[idx]) hasAny = true;
5827
+ }
5828
+ const diagonal = masterBorder?.diagonal;
5829
+ const color = masterBorder?.color;
5830
+ if (!hasAny && !diagonal) return;
5831
+ return {
5832
+ topEdges,
5833
+ bottomEdges,
5834
+ leftEdges,
5835
+ rightEdges,
5836
+ diagonal,
5837
+ color
5838
+ };
5839
+ }
5840
+ /**
5841
+ * Apply position-aware borders to a merged cell range.
5842
+ * Must be called AFTER cell.merge() so that the master style is available.
5843
+ *
5844
+ * Each cell receives a deep-copied style from the master so that
5845
+ * later mutations to one cell do not leak to others.
5846
+ */
5847
+ function applyMergeBorders(top, left, bottom, right, collected, getCell) {
5848
+ const { topEdges, bottomEdges, leftEdges, rightEdges, diagonal, color } = collected;
5849
+ const masterStyle = getCell(top, left).style;
5850
+ for (let i = top; i <= bottom; i++) for (let j = left; j <= right; j++) {
5851
+ const cell = getCell(i, j);
5852
+ const style = copyStyle(masterStyle) || {};
5853
+ const newBorder = {};
5854
+ let hasBorder = false;
5855
+ if (i === top && topEdges[j - left]) {
5856
+ newBorder.top = topEdges[j - left];
5857
+ hasBorder = true;
5858
+ }
5859
+ if (i === bottom && bottomEdges[j - left]) {
5860
+ newBorder.bottom = bottomEdges[j - left];
5861
+ hasBorder = true;
5862
+ }
5863
+ if (j === left && leftEdges[i - top]) {
5864
+ newBorder.left = leftEdges[i - top];
5865
+ hasBorder = true;
5866
+ }
5867
+ if (j === right && rightEdges[i - top]) {
5868
+ newBorder.right = rightEdges[i - top];
5869
+ hasBorder = true;
5870
+ }
5871
+ if (diagonal) {
5872
+ newBorder.diagonal = diagonal;
5873
+ hasBorder = true;
5874
+ }
5875
+ if (hasBorder) {
5876
+ if (color) newBorder.color = color;
5877
+ style.border = newBorder;
5878
+ } else delete style.border;
5879
+ cell.style = style;
5880
+ }
5881
+ }
5882
+
5760
5883
  //#endregion
5761
5884
  //#region src/modules/excel/worksheet.ts
5762
5885
  var Worksheet = class {
@@ -6273,8 +6396,11 @@ var ExcelTS = (function(exports) {
6273
6396
  Object.values(this._merges).forEach((merge) => {
6274
6397
  if (merge.intersects(dimensions)) throw new Error("Cannot merge already merged cells");
6275
6398
  });
6399
+ const { top, left, bottom, right } = dimensions;
6400
+ const collected = ignoreStyle ? void 0 : collectMergeBorders(top, left, bottom, right, (r, c) => this.findCell(r, c));
6276
6401
  const master = this.getCell(dimensions.top, dimensions.left);
6277
- for (let i = dimensions.top; i <= dimensions.bottom; i++) for (let j = dimensions.left; j <= dimensions.right; j++) if (i > dimensions.top || j > dimensions.left) this.getCell(i, j).merge(master, ignoreStyle);
6402
+ for (let i = top; i <= bottom; i++) for (let j = left; j <= right; j++) if (i > top || j > left) this.getCell(i, j).merge(master, ignoreStyle);
6403
+ if (collected) applyMergeBorders(top, left, bottom, right, collected, (r, c) => this.getCell(r, c));
6278
6404
  this._merges[master.address] = dimensions;
6279
6405
  }
6280
6406
  _unMergeMaster(master) {
@@ -24301,8 +24427,11 @@ var ExcelTS = (function(exports) {
24301
24427
  this._merges.forEach((merge) => {
24302
24428
  if (merge.intersects(dimensions)) throw new Error("Cannot merge already merged cells");
24303
24429
  });
24304
- const master = this.getCell(dimensions.top, dimensions.left);
24305
- for (let i = dimensions.top; i <= dimensions.bottom; i++) for (let j = dimensions.left; j <= dimensions.right; j++) if (i > dimensions.top || j > dimensions.left) this.getCell(i, j).merge(master);
24430
+ const { top, left, bottom, right } = dimensions;
24431
+ const collected = collectMergeBorders(top, left, bottom, right, (r, c) => this.findCell(r, c));
24432
+ const master = this.getCell(top, left);
24433
+ for (let i = top; i <= bottom; i++) for (let j = left; j <= right; j++) if (i > top || j > left) this.getCell(i, j).merge(master);
24434
+ if (collected) applyMergeBorders(top, left, bottom, right, collected, (r, c) => this.getCell(r, c));
24306
24435
  this._merges.push(dimensions);
24307
24436
  }
24308
24437
  addConditionalFormatting(cf) {
@@ -27474,12 +27603,12 @@ onmessage = async (ev) => {
27474
27603
  */
27475
27604
  function chooseFormat(fmt, val) {
27476
27605
  if (typeof val === "string") {
27477
- const sections = splitFormat(fmt);
27606
+ const sections = splitFormatSections(fmt);
27478
27607
  if (sections.length >= 4 && sections[3]) return processQuotedText(sections[3]).replace(/@/g, val);
27479
27608
  return val;
27480
27609
  }
27481
27610
  if (typeof val === "boolean") return val ? "TRUE" : "FALSE";
27482
- const sections = splitFormat(fmt);
27611
+ const sections = splitFormatSections(fmt);
27483
27612
  const condRegex = /\[(=|>|<|>=|<=|<>)-?\d+(?:\.\d*)?\]/;
27484
27613
  if ((sections[0] && condRegex.test(sections[0]) || sections[1] && condRegex.test(sections[1])) && sections.length >= 2) {
27485
27614
  for (let i = 0; i < Math.min(sections.length, 2); i++) {
@@ -27498,37 +27627,10 @@ onmessage = async (ev) => {
27498
27627
  * Check if format section is for negative values (2nd section in multi-section format)
27499
27628
  */
27500
27629
  function isNegativeSection(fmt, selectedFmt) {
27501
- const sections = splitFormat(fmt);
27630
+ const sections = splitFormatSections(fmt);
27502
27631
  return sections.length >= 2 && sections[1] === selectedFmt;
27503
27632
  }
27504
27633
  /**
27505
- * Split format string by semicolons, respecting quoted strings and brackets
27506
- */
27507
- function splitFormat(fmt) {
27508
- const sections = [];
27509
- let current = "";
27510
- let inQuote = false;
27511
- let inBracket = false;
27512
- for (let i = 0; i < fmt.length; i++) {
27513
- const char = fmt[i];
27514
- if (char === "\"" && !inBracket) {
27515
- inQuote = !inQuote;
27516
- current += char;
27517
- } else if (char === "[" && !inQuote) {
27518
- inBracket = true;
27519
- current += char;
27520
- } else if (char === "]" && !inQuote) {
27521
- inBracket = false;
27522
- current += char;
27523
- } else if (char === ";" && !inQuote && !inBracket) {
27524
- sections.push(current);
27525
- current = "";
27526
- } else current += char;
27527
- }
27528
- sections.push(current);
27529
- return sections;
27530
- }
27531
- /**
27532
27634
  * Main format function - formats a value according to Excel numFmt
27533
27635
  * @param fmt The Excel number format string (e.g., "0.00%", "#,##0", "yyyy-mm-dd")
27534
27636
  * @param val The value to format