@cj-tech-master/excelts 5.1.2 → 5.1.3

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.
@@ -26,6 +26,7 @@ declare class Anchor {
26
26
  get rowHeight(): number;
27
27
  get model(): AnchorModel;
28
28
  set model(value: AnchorModel);
29
+ clone(worksheet?: Worksheet): Anchor;
29
30
  }
30
31
  export { Anchor };
31
32
  export type { AnchorModel };
@@ -93,5 +93,8 @@ class Anchor {
93
93
  this.nativeRow = value.nativeRow;
94
94
  this.nativeRowOff = value.nativeRowOff;
95
95
  }
96
+ clone(worksheet) {
97
+ return new Anchor(worksheet ?? this.worksheet, this.model);
98
+ }
96
99
  }
97
100
  export { Anchor };
@@ -60,5 +60,6 @@ declare class Image {
60
60
  constructor(worksheet: Worksheet, model?: ModelInput);
61
61
  get model(): Model;
62
62
  set model({ type, imageId, range, hyperlinks }: ModelInput);
63
+ clone(worksheet?: Worksheet): Image;
63
64
  }
64
65
  export { Image, type Model as ImageModel, type ImageModelInput };
@@ -56,5 +56,21 @@ class Image {
56
56
  }
57
57
  }
58
58
  }
59
+ clone(worksheet) {
60
+ const target = worksheet ?? this.worksheet;
61
+ const cloned = new Image(target);
62
+ cloned.type = this.type;
63
+ cloned.imageId = this.imageId;
64
+ if (this.range) {
65
+ cloned.range = {
66
+ tl: this.range.tl.clone(target),
67
+ br: this.range.br ? this.range.br.clone(target) : undefined,
68
+ ext: this.range.ext ? { ...this.range.ext } : undefined,
69
+ editAs: this.range.editAs,
70
+ hyperlinks: this.range.hyperlinks ? { ...this.range.hyperlinks } : undefined
71
+ };
72
+ }
73
+ return cloned;
74
+ }
59
75
  }
60
76
  export { Image };
@@ -492,6 +492,17 @@ class Worksheet {
492
492
  srcMerges.push(merge);
493
493
  }
494
494
  }
495
+ // Collect images anchored to the source row before splicing
496
+ // (images whose top-left anchor is on the source row)
497
+ const srcImages = [];
498
+ const srcRow0 = rowNum - 1; // 0-based source row
499
+ for (const image of this._media) {
500
+ if (image.type === "image" && image.range) {
501
+ if (image.range.tl.nativeRow === srcRow0) {
502
+ srcImages.push(image);
503
+ }
504
+ }
505
+ }
495
506
  this.spliceRows(rowNum + 1, insert ? 0 : count, ...inserts);
496
507
  // now copy styles...
497
508
  for (let i = 0; i < count; i++) {
@@ -523,6 +534,32 @@ class Worksheet {
523
534
  }
524
535
  }
525
536
  }
537
+ // Duplicate images from source row into each new row.
538
+ // In overwrite mode, first remove any images anchored to the target rows
539
+ // so they don't coexist with the clones (mirrors merge cleanup above).
540
+ if (!insert) {
541
+ const dstStart0 = rowNum; // first target row, 0-based (1-based rowNum + 1 → 0-based rowNum)
542
+ const dstEnd0 = rowNum + count - 1; // last target row, 0-based
543
+ this._media = this._media.filter(image => {
544
+ if (image.type === "image" && image.range) {
545
+ const row0 = image.range.tl.nativeRow;
546
+ return row0 < dstStart0 || row0 > dstEnd0;
547
+ }
548
+ return true;
549
+ });
550
+ }
551
+ for (let i = 0; i < count; i++) {
552
+ const rowDelta = i + 1; // offset from source row to target row
553
+ for (const srcImage of srcImages) {
554
+ const cloned = srcImage.clone();
555
+ cloned.range.tl.nativeRow = srcRow0 + rowDelta;
556
+ if (cloned.range.br) {
557
+ const brDelta = srcImage.range.br.nativeRow - srcRow0;
558
+ cloned.range.br.nativeRow = srcRow0 + rowDelta + brDelta;
559
+ }
560
+ this._media.push(cloned);
561
+ }
562
+ }
526
563
  }
527
564
  /**
528
565
  * Cut one or more rows (rows below are shifted up)
@@ -188,16 +188,29 @@ class WorkSheetXform extends BaseXform {
188
188
  vmlDrawing: `vmlDrawing${model.id}`
189
189
  });
190
190
  }
191
- // Handle pre-loaded drawing (from file read) that may contain charts or other non-image content
192
- // This preserves drawings through round-trip even if they don't contain images
193
- // Note: Always assign a new rId since rels are rebuilt during write
191
+ // Handle pre-loaded drawing (from file read) that may contain charts or other non-image content.
192
+ // Reset anchors and rels so they are rebuilt cleanly from model.media (images) and
193
+ // model.formControls (shapes) below. Without this reset, every read-write cycle would
194
+ // duplicate image anchors because the same images exist in both model.drawing.anchors
195
+ // (preserved for round-trip) and model.media (the canonical image list).
196
+ // For chart drawings, rels are preserved because the raw XML passthrough references
197
+ // original rIds; anchors are still cleared since they are unused for chart drawings.
194
198
  if (model.drawing && model.drawing.anchors) {
195
- // This is a loaded drawing that needs to be added to relationships
196
199
  const drawing = model.drawing;
197
200
  drawing.rId = nextRid(rels);
198
201
  if (!drawing.name) {
199
202
  drawing.name = `drawing${++options.drawingsCount}`;
200
203
  }
204
+ const hasChartRels = (drawing.rels ?? []).some((rel) => rel.Target && rel.Target.includes("/charts/"));
205
+ // Anchors are always reset: for chart drawings they are unused (raw XML passthrough),
206
+ // for normal drawings they are rebuilt from model.media below.
207
+ drawing.anchors = [];
208
+ if (!hasChartRels) {
209
+ // Non-chart drawings: clear rels so image rels are rebuilt from scratch.
210
+ drawing.rels = [];
211
+ }
212
+ // Chart drawings keep their original rels intact since the raw drawing XML
213
+ // references those rIds directly.
201
214
  options.drawings.push(drawing);
202
215
  rels.push({
203
216
  Id: drawing.rId,
@@ -96,5 +96,8 @@ class Anchor {
96
96
  this.nativeRow = value.nativeRow;
97
97
  this.nativeRowOff = value.nativeRowOff;
98
98
  }
99
+ clone(worksheet) {
100
+ return new Anchor(worksheet ?? this.worksheet, this.model);
101
+ }
99
102
  }
100
103
  exports.Anchor = Anchor;
@@ -59,5 +59,21 @@ class Image {
59
59
  }
60
60
  }
61
61
  }
62
+ clone(worksheet) {
63
+ const target = worksheet ?? this.worksheet;
64
+ const cloned = new Image(target);
65
+ cloned.type = this.type;
66
+ cloned.imageId = this.imageId;
67
+ if (this.range) {
68
+ cloned.range = {
69
+ tl: this.range.tl.clone(target),
70
+ br: this.range.br ? this.range.br.clone(target) : undefined,
71
+ ext: this.range.ext ? { ...this.range.ext } : undefined,
72
+ editAs: this.range.editAs,
73
+ hyperlinks: this.range.hyperlinks ? { ...this.range.hyperlinks } : undefined
74
+ };
75
+ }
76
+ return cloned;
77
+ }
62
78
  }
63
79
  exports.Image = Image;
@@ -495,6 +495,17 @@ class Worksheet {
495
495
  srcMerges.push(merge);
496
496
  }
497
497
  }
498
+ // Collect images anchored to the source row before splicing
499
+ // (images whose top-left anchor is on the source row)
500
+ const srcImages = [];
501
+ const srcRow0 = rowNum - 1; // 0-based source row
502
+ for (const image of this._media) {
503
+ if (image.type === "image" && image.range) {
504
+ if (image.range.tl.nativeRow === srcRow0) {
505
+ srcImages.push(image);
506
+ }
507
+ }
508
+ }
498
509
  this.spliceRows(rowNum + 1, insert ? 0 : count, ...inserts);
499
510
  // now copy styles...
500
511
  for (let i = 0; i < count; i++) {
@@ -526,6 +537,32 @@ class Worksheet {
526
537
  }
527
538
  }
528
539
  }
540
+ // Duplicate images from source row into each new row.
541
+ // In overwrite mode, first remove any images anchored to the target rows
542
+ // so they don't coexist with the clones (mirrors merge cleanup above).
543
+ if (!insert) {
544
+ const dstStart0 = rowNum; // first target row, 0-based (1-based rowNum + 1 → 0-based rowNum)
545
+ const dstEnd0 = rowNum + count - 1; // last target row, 0-based
546
+ this._media = this._media.filter(image => {
547
+ if (image.type === "image" && image.range) {
548
+ const row0 = image.range.tl.nativeRow;
549
+ return row0 < dstStart0 || row0 > dstEnd0;
550
+ }
551
+ return true;
552
+ });
553
+ }
554
+ for (let i = 0; i < count; i++) {
555
+ const rowDelta = i + 1; // offset from source row to target row
556
+ for (const srcImage of srcImages) {
557
+ const cloned = srcImage.clone();
558
+ cloned.range.tl.nativeRow = srcRow0 + rowDelta;
559
+ if (cloned.range.br) {
560
+ const brDelta = srcImage.range.br.nativeRow - srcRow0;
561
+ cloned.range.br.nativeRow = srcRow0 + rowDelta + brDelta;
562
+ }
563
+ this._media.push(cloned);
564
+ }
565
+ }
529
566
  }
530
567
  /**
531
568
  * Cut one or more rows (rows below are shifted up)
@@ -191,16 +191,29 @@ class WorkSheetXform extends base_xform_1.BaseXform {
191
191
  vmlDrawing: `vmlDrawing${model.id}`
192
192
  });
193
193
  }
194
- // Handle pre-loaded drawing (from file read) that may contain charts or other non-image content
195
- // This preserves drawings through round-trip even if they don't contain images
196
- // Note: Always assign a new rId since rels are rebuilt during write
194
+ // Handle pre-loaded drawing (from file read) that may contain charts or other non-image content.
195
+ // Reset anchors and rels so they are rebuilt cleanly from model.media (images) and
196
+ // model.formControls (shapes) below. Without this reset, every read-write cycle would
197
+ // duplicate image anchors because the same images exist in both model.drawing.anchors
198
+ // (preserved for round-trip) and model.media (the canonical image list).
199
+ // For chart drawings, rels are preserved because the raw XML passthrough references
200
+ // original rIds; anchors are still cleared since they are unused for chart drawings.
197
201
  if (model.drawing && model.drawing.anchors) {
198
- // This is a loaded drawing that needs to be added to relationships
199
202
  const drawing = model.drawing;
200
203
  drawing.rId = nextRid(rels);
201
204
  if (!drawing.name) {
202
205
  drawing.name = `drawing${++options.drawingsCount}`;
203
206
  }
207
+ const hasChartRels = (drawing.rels ?? []).some((rel) => rel.Target && rel.Target.includes("/charts/"));
208
+ // Anchors are always reset: for chart drawings they are unused (raw XML passthrough),
209
+ // for normal drawings they are rebuilt from model.media below.
210
+ drawing.anchors = [];
211
+ if (!hasChartRels) {
212
+ // Non-chart drawings: clear rels so image rels are rebuilt from scratch.
213
+ drawing.rels = [];
214
+ }
215
+ // Chart drawings keep their original rels intact since the raw drawing XML
216
+ // references those rIds directly.
204
217
  options.drawings.push(drawing);
205
218
  rels.push({
206
219
  Id: drawing.rId,
@@ -93,5 +93,8 @@ class Anchor {
93
93
  this.nativeRow = value.nativeRow;
94
94
  this.nativeRowOff = value.nativeRowOff;
95
95
  }
96
+ clone(worksheet) {
97
+ return new Anchor(worksheet ?? this.worksheet, this.model);
98
+ }
96
99
  }
97
100
  export { Anchor };
@@ -56,5 +56,21 @@ class Image {
56
56
  }
57
57
  }
58
58
  }
59
+ clone(worksheet) {
60
+ const target = worksheet ?? this.worksheet;
61
+ const cloned = new Image(target);
62
+ cloned.type = this.type;
63
+ cloned.imageId = this.imageId;
64
+ if (this.range) {
65
+ cloned.range = {
66
+ tl: this.range.tl.clone(target),
67
+ br: this.range.br ? this.range.br.clone(target) : undefined,
68
+ ext: this.range.ext ? { ...this.range.ext } : undefined,
69
+ editAs: this.range.editAs,
70
+ hyperlinks: this.range.hyperlinks ? { ...this.range.hyperlinks } : undefined
71
+ };
72
+ }
73
+ return cloned;
74
+ }
59
75
  }
60
76
  export { Image };
@@ -492,6 +492,17 @@ class Worksheet {
492
492
  srcMerges.push(merge);
493
493
  }
494
494
  }
495
+ // Collect images anchored to the source row before splicing
496
+ // (images whose top-left anchor is on the source row)
497
+ const srcImages = [];
498
+ const srcRow0 = rowNum - 1; // 0-based source row
499
+ for (const image of this._media) {
500
+ if (image.type === "image" && image.range) {
501
+ if (image.range.tl.nativeRow === srcRow0) {
502
+ srcImages.push(image);
503
+ }
504
+ }
505
+ }
495
506
  this.spliceRows(rowNum + 1, insert ? 0 : count, ...inserts);
496
507
  // now copy styles...
497
508
  for (let i = 0; i < count; i++) {
@@ -523,6 +534,32 @@ class Worksheet {
523
534
  }
524
535
  }
525
536
  }
537
+ // Duplicate images from source row into each new row.
538
+ // In overwrite mode, first remove any images anchored to the target rows
539
+ // so they don't coexist with the clones (mirrors merge cleanup above).
540
+ if (!insert) {
541
+ const dstStart0 = rowNum; // first target row, 0-based (1-based rowNum + 1 → 0-based rowNum)
542
+ const dstEnd0 = rowNum + count - 1; // last target row, 0-based
543
+ this._media = this._media.filter(image => {
544
+ if (image.type === "image" && image.range) {
545
+ const row0 = image.range.tl.nativeRow;
546
+ return row0 < dstStart0 || row0 > dstEnd0;
547
+ }
548
+ return true;
549
+ });
550
+ }
551
+ for (let i = 0; i < count; i++) {
552
+ const rowDelta = i + 1; // offset from source row to target row
553
+ for (const srcImage of srcImages) {
554
+ const cloned = srcImage.clone();
555
+ cloned.range.tl.nativeRow = srcRow0 + rowDelta;
556
+ if (cloned.range.br) {
557
+ const brDelta = srcImage.range.br.nativeRow - srcRow0;
558
+ cloned.range.br.nativeRow = srcRow0 + rowDelta + brDelta;
559
+ }
560
+ this._media.push(cloned);
561
+ }
562
+ }
526
563
  }
527
564
  /**
528
565
  * Cut one or more rows (rows below are shifted up)
@@ -188,16 +188,29 @@ class WorkSheetXform extends BaseXform {
188
188
  vmlDrawing: `vmlDrawing${model.id}`
189
189
  });
190
190
  }
191
- // Handle pre-loaded drawing (from file read) that may contain charts or other non-image content
192
- // This preserves drawings through round-trip even if they don't contain images
193
- // Note: Always assign a new rId since rels are rebuilt during write
191
+ // Handle pre-loaded drawing (from file read) that may contain charts or other non-image content.
192
+ // Reset anchors and rels so they are rebuilt cleanly from model.media (images) and
193
+ // model.formControls (shapes) below. Without this reset, every read-write cycle would
194
+ // duplicate image anchors because the same images exist in both model.drawing.anchors
195
+ // (preserved for round-trip) and model.media (the canonical image list).
196
+ // For chart drawings, rels are preserved because the raw XML passthrough references
197
+ // original rIds; anchors are still cleared since they are unused for chart drawings.
194
198
  if (model.drawing && model.drawing.anchors) {
195
- // This is a loaded drawing that needs to be added to relationships
196
199
  const drawing = model.drawing;
197
200
  drawing.rId = nextRid(rels);
198
201
  if (!drawing.name) {
199
202
  drawing.name = `drawing${++options.drawingsCount}`;
200
203
  }
204
+ const hasChartRels = (drawing.rels ?? []).some((rel) => rel.Target && rel.Target.includes("/charts/"));
205
+ // Anchors are always reset: for chart drawings they are unused (raw XML passthrough),
206
+ // for normal drawings they are rebuilt from model.media below.
207
+ drawing.anchors = [];
208
+ if (!hasChartRels) {
209
+ // Non-chart drawings: clear rels so image rels are rebuilt from scratch.
210
+ drawing.rels = [];
211
+ }
212
+ // Chart drawings keep their original rels intact since the raw drawing XML
213
+ // references those rIds directly.
201
214
  options.drawings.push(drawing);
202
215
  rels.push({
203
216
  Id: drawing.rId,
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @cj-tech-master/excelts v5.1.2
2
+ * @cj-tech-master/excelts v5.1.3
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
@@ -2239,11 +2239,14 @@ var ExcelTS = (function(exports) {
2239
2239
  this.nativeRow = value.nativeRow;
2240
2240
  this.nativeRowOff = value.nativeRowOff;
2241
2241
  }
2242
+ clone(worksheet) {
2243
+ return new Anchor(worksheet ?? this.worksheet, this.model);
2244
+ }
2242
2245
  };
2243
2246
 
2244
2247
  //#endregion
2245
2248
  //#region src/modules/excel/image.ts
2246
- var Image = class {
2249
+ var Image = class Image {
2247
2250
  constructor(worksheet, model) {
2248
2251
  this.worksheet = worksheet;
2249
2252
  if (model) this.model = model;
@@ -2294,6 +2297,20 @@ var ExcelTS = (function(exports) {
2294
2297
  };
2295
2298
  }
2296
2299
  }
2300
+ clone(worksheet) {
2301
+ const target = worksheet ?? this.worksheet;
2302
+ const cloned = new Image(target);
2303
+ cloned.type = this.type;
2304
+ cloned.imageId = this.imageId;
2305
+ if (this.range) cloned.range = {
2306
+ tl: this.range.tl.clone(target),
2307
+ br: this.range.br ? this.range.br.clone(target) : void 0,
2308
+ ext: this.range.ext ? { ...this.range.ext } : void 0,
2309
+ editAs: this.range.editAs,
2310
+ hyperlinks: this.range.hyperlinks ? { ...this.range.hyperlinks } : void 0
2311
+ };
2312
+ return cloned;
2313
+ }
2297
2314
  };
2298
2315
 
2299
2316
  //#endregion
@@ -6086,6 +6103,11 @@ var ExcelTS = (function(exports) {
6086
6103
  const inserts = Array.from({ length: count }).fill(rSrc.values);
6087
6104
  const srcMerges = [];
6088
6105
  for (const merge of Object.values(this._merges)) if (merge.top === rowNum && merge.bottom === rowNum) srcMerges.push(merge);
6106
+ const srcImages = [];
6107
+ const srcRow0 = rowNum - 1;
6108
+ for (const image of this._media) if (image.type === "image" && image.range) {
6109
+ if (image.range.tl.nativeRow === srcRow0) srcImages.push(image);
6110
+ }
6089
6111
  this.spliceRows(rowNum + 1, insert ? 0 : count, ...inserts);
6090
6112
  for (let i = 0; i < count; i++) {
6091
6113
  const rDst = this._rows[rowNum + i];
@@ -6104,6 +6126,29 @@ var ExcelTS = (function(exports) {
6104
6126
  }
6105
6127
  for (const srcMerge of srcMerges) this.mergeCellsWithoutStyle(dstRow, srcMerge.left, dstRow, srcMerge.right);
6106
6128
  }
6129
+ if (!insert) {
6130
+ const dstStart0 = rowNum;
6131
+ const dstEnd0 = rowNum + count - 1;
6132
+ this._media = this._media.filter((image) => {
6133
+ if (image.type === "image" && image.range) {
6134
+ const row0 = image.range.tl.nativeRow;
6135
+ return row0 < dstStart0 || row0 > dstEnd0;
6136
+ }
6137
+ return true;
6138
+ });
6139
+ }
6140
+ for (let i = 0; i < count; i++) {
6141
+ const rowDelta = i + 1;
6142
+ for (const srcImage of srcImages) {
6143
+ const cloned = srcImage.clone();
6144
+ cloned.range.tl.nativeRow = srcRow0 + rowDelta;
6145
+ if (cloned.range.br) {
6146
+ const brDelta = srcImage.range.br.nativeRow - srcRow0;
6147
+ cloned.range.br.nativeRow = srcRow0 + rowDelta + brDelta;
6148
+ }
6149
+ this._media.push(cloned);
6150
+ }
6151
+ }
6107
6152
  }
6108
6153
  /**
6109
6154
  * Cut one or more rows (rows below are shifted up)
@@ -14219,6 +14264,9 @@ var ExcelTS = (function(exports) {
14219
14264
  const drawing = model.drawing;
14220
14265
  drawing.rId = nextRid(rels);
14221
14266
  if (!drawing.name) drawing.name = `drawing${++options.drawingsCount}`;
14267
+ const hasChartRels = (drawing.rels ?? []).some((rel) => rel.Target && rel.Target.includes("/charts/"));
14268
+ drawing.anchors = [];
14269
+ if (!hasChartRels) drawing.rels = [];
14222
14270
  options.drawings.push(drawing);
14223
14271
  rels.push({
14224
14272
  Id: drawing.rId,