@productcloudos/editor 1.0.4 → 1.0.5

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 (45) hide show
  1. package/dist/pc-editor.esm.js +497 -209
  2. package/dist/pc-editor.esm.js.map +1 -1
  3. package/dist/pc-editor.js +497 -208
  4. package/dist/pc-editor.js.map +1 -1
  5. package/dist/pc-editor.min.js +1 -1
  6. package/dist/pc-editor.min.js.map +1 -1
  7. package/dist/types/lib/core/PCEditor.d.ts +16 -5
  8. package/dist/types/lib/core/PCEditor.d.ts.map +1 -1
  9. package/dist/types/lib/import/PDFParser.d.ts.map +1 -1
  10. package/dist/types/lib/index.d.ts +1 -0
  11. package/dist/types/lib/index.d.ts.map +1 -1
  12. package/dist/types/lib/objects/EmbeddedObjectFactory.d.ts.map +1 -1
  13. package/dist/types/lib/objects/ImageObject.d.ts +11 -0
  14. package/dist/types/lib/objects/ImageObject.d.ts.map +1 -1
  15. package/dist/types/lib/objects/TextBoxObject.d.ts +5 -2
  16. package/dist/types/lib/objects/TextBoxObject.d.ts.map +1 -1
  17. package/dist/types/lib/objects/table/TableCell.d.ts.map +1 -1
  18. package/dist/types/lib/objects/table/TableObject.d.ts.map +1 -1
  19. package/dist/types/lib/objects/table/TableRow.d.ts.map +1 -1
  20. package/dist/types/lib/objects/table/types.d.ts +15 -15
  21. package/dist/types/lib/objects/table/types.d.ts.map +1 -1
  22. package/dist/types/lib/panes/DocumentInfoPane.d.ts +3 -1
  23. package/dist/types/lib/panes/DocumentInfoPane.d.ts.map +1 -1
  24. package/dist/types/lib/panes/DocumentSettingsPane.d.ts +2 -0
  25. package/dist/types/lib/panes/DocumentSettingsPane.d.ts.map +1 -1
  26. package/dist/types/lib/panes/HyperlinkPane.d.ts +1 -0
  27. package/dist/types/lib/panes/HyperlinkPane.d.ts.map +1 -1
  28. package/dist/types/lib/panes/ImagePane.d.ts +1 -0
  29. package/dist/types/lib/panes/ImagePane.d.ts.map +1 -1
  30. package/dist/types/lib/panes/TablePane.d.ts +3 -0
  31. package/dist/types/lib/panes/TablePane.d.ts.map +1 -1
  32. package/dist/types/lib/panes/TextBoxPane.d.ts +1 -0
  33. package/dist/types/lib/panes/TextBoxPane.d.ts.map +1 -1
  34. package/dist/types/lib/rendering/CanvasManager.d.ts.map +1 -1
  35. package/dist/types/lib/rendering/FlowingTextRenderer.d.ts.map +1 -1
  36. package/dist/types/lib/rendering/PDFGenerator.d.ts.map +1 -1
  37. package/dist/types/lib/text/FlowingTextContent.d.ts.map +1 -1
  38. package/dist/types/lib/text/TextFormatting.d.ts +12 -1
  39. package/dist/types/lib/text/TextFormatting.d.ts.map +1 -1
  40. package/dist/types/lib/types/index.d.ts +2 -0
  41. package/dist/types/lib/types/index.d.ts.map +1 -1
  42. package/dist/types/lib/undo/transaction/MutationUndo.d.ts.map +1 -1
  43. package/dist/types/lib/utils/logger.d.ts +20 -0
  44. package/dist/types/lib/utils/logger.d.ts.map +1 -0
  45. package/package.json +1 -1
package/dist/pc-editor.js CHANGED
@@ -839,12 +839,13 @@ class TextFormattingManager extends EventEmitter {
839
839
  }
840
840
  /**
841
841
  * Get formatting at a specific character position.
842
- * Returns the position-specific formatting or the default.
842
+ * Returns the position-specific formatting merged with defaults,
843
+ * ensuring all properties are consistently present.
843
844
  */
844
845
  getFormattingAt(position) {
845
846
  const override = this.formatting.get(position);
846
847
  if (override) {
847
- return { ...override };
848
+ return { ...this._defaultFormatting, ...override };
848
849
  }
849
850
  return { ...this._defaultFormatting };
850
851
  }
@@ -931,6 +932,43 @@ class TextFormattingManager extends EventEmitter {
931
932
  getAllFormatting() {
932
933
  return new Map(this.formatting);
933
934
  }
935
+ /**
936
+ * Get formatting as compressed runs for serialization.
937
+ * Only outputs entries where formatting changes from the previous character.
938
+ * Skips leading default formatting to minimize output size.
939
+ * @param textLength Length of the text to serialize formatting for
940
+ */
941
+ getCompressedRuns(textLength) {
942
+ const runs = [];
943
+ const defaultFormat = this._defaultFormatting;
944
+ let lastFormat = null;
945
+ for (let i = 0; i < textLength; i++) {
946
+ const currentFormat = this.getFormattingAt(i);
947
+ const formatChanged = lastFormat === null ||
948
+ currentFormat.fontFamily !== lastFormat.fontFamily ||
949
+ currentFormat.fontSize !== lastFormat.fontSize ||
950
+ currentFormat.fontWeight !== lastFormat.fontWeight ||
951
+ currentFormat.fontStyle !== lastFormat.fontStyle ||
952
+ currentFormat.color !== lastFormat.color ||
953
+ currentFormat.backgroundColor !== lastFormat.backgroundColor;
954
+ if (formatChanged) {
955
+ const isDefault = currentFormat.fontFamily === defaultFormat.fontFamily &&
956
+ currentFormat.fontSize === defaultFormat.fontSize &&
957
+ currentFormat.fontWeight === defaultFormat.fontWeight &&
958
+ currentFormat.fontStyle === defaultFormat.fontStyle &&
959
+ currentFormat.color === defaultFormat.color &&
960
+ currentFormat.backgroundColor === defaultFormat.backgroundColor;
961
+ if (!isDefault || runs.length > 0) {
962
+ runs.push({
963
+ index: i,
964
+ formatting: { ...currentFormat }
965
+ });
966
+ }
967
+ lastFormat = currentFormat;
968
+ }
969
+ }
970
+ return runs;
971
+ }
934
972
  /**
935
973
  * Restore formatting from a map (for deserialization).
936
974
  */
@@ -3834,6 +3872,35 @@ class ImageObject extends BaseEmbeddedObject {
3834
3872
  get hasError() {
3835
3873
  return this._error;
3836
3874
  }
3875
+ /**
3876
+ * Get the loaded HTMLImageElement, if available.
3877
+ * Used by PDFGenerator to convert unsupported formats to PNG.
3878
+ */
3879
+ get imageElement() {
3880
+ return this._loaded ? this._image : null;
3881
+ }
3882
+ /**
3883
+ * Convert the image to a PNG data URL via canvas.
3884
+ * Used when the original format (e.g., SVG, WebP, GIF) is not supported by pdf-lib.
3885
+ * Returns null if the image is not loaded or conversion fails.
3886
+ */
3887
+ toPngDataUrl() {
3888
+ if (!this._loaded || !this._image)
3889
+ return null;
3890
+ try {
3891
+ const canvas = document.createElement('canvas');
3892
+ canvas.width = this._image.naturalWidth || this._size.width;
3893
+ canvas.height = this._image.naturalHeight || this._size.height;
3894
+ const ctx = canvas.getContext('2d');
3895
+ if (!ctx)
3896
+ return null;
3897
+ ctx.drawImage(this._image, 0, 0, canvas.width, canvas.height);
3898
+ return canvas.toDataURL('image/png');
3899
+ }
3900
+ catch {
3901
+ return null;
3902
+ }
3903
+ }
3837
3904
  loadImage() {
3838
3905
  if (!this._src) {
3839
3906
  this._error = true;
@@ -4729,12 +4796,10 @@ class TextBoxObject extends BaseEmbeddedObject {
4729
4796
  this.editing = false;
4730
4797
  }
4731
4798
  toData() {
4732
- // Serialize formatting map to array of [index, style] pairs
4733
- const formattingMap = this._flowingContent.getFormattingManager().getAllFormatting();
4734
- const formattingEntries = [];
4735
- formattingMap.forEach((value, key) => {
4736
- formattingEntries.push([key, { ...value }]);
4737
- });
4799
+ // Serialize formatting as compressed runs (only at change boundaries)
4800
+ const text = this._flowingContent.getText();
4801
+ const compressedRuns = this._flowingContent.getFormattingManager().getCompressedRuns(text.length);
4802
+ const formattingEntries = compressedRuns.map(run => [run.index, { ...run.formatting }]);
4738
4803
  // Get substitution fields as array
4739
4804
  const fields = this._flowingContent.getSubstitutionFieldManager().getFieldsArray();
4740
4805
  return {
@@ -4793,12 +4858,19 @@ class TextBoxObject extends BaseEmbeddedObject {
4793
4858
  this._border = { ...boxData.border };
4794
4859
  if (boxData.padding !== undefined)
4795
4860
  this._padding = boxData.padding;
4796
- // Restore formatting runs
4797
- if (boxData.formattingRuns) {
4861
+ // Restore formatting runs (run-based: each entry applies from its index to the next)
4862
+ if (boxData.formattingRuns && boxData.formattingRuns.length > 0) {
4798
4863
  const formattingManager = this._flowingContent.getFormattingManager();
4799
4864
  formattingManager.clear();
4800
- for (const [index, style] of boxData.formattingRuns) {
4801
- formattingManager.applyFormatting(index, index + 1, style);
4865
+ const textLength = this._flowingContent.getText().length;
4866
+ for (let i = 0; i < boxData.formattingRuns.length; i++) {
4867
+ const [startIndex, style] = boxData.formattingRuns[i];
4868
+ const nextIndex = i + 1 < boxData.formattingRuns.length
4869
+ ? boxData.formattingRuns[i + 1][0]
4870
+ : textLength;
4871
+ if (startIndex < nextIndex) {
4872
+ formattingManager.applyFormatting(startIndex, nextIndex, style);
4873
+ }
4802
4874
  }
4803
4875
  }
4804
4876
  // Restore substitution fields
@@ -4948,15 +5020,19 @@ class TextBoxObject extends BaseEmbeddedObject {
4948
5020
  }
4949
5021
  /**
4950
5022
  * Check if a point is within this text box region.
5023
+ * Uses the full object bounds (including padding/border) so that
5024
+ * double-click to enter edit mode works on the entire text box area,
5025
+ * not just the inner text area.
4951
5026
  * @param point Point in canvas coordinates
4952
- * @param pageIndex The page index (ignored for text boxes)
5027
+ * @param _pageIndex The page index (ignored for text boxes)
4953
5028
  */
4954
- containsPointInRegion(point, pageIndex) {
4955
- const bounds = this.getRegionBounds(pageIndex);
4956
- if (!bounds)
5029
+ containsPointInRegion(point, _pageIndex) {
5030
+ if (!this._renderedPosition)
4957
5031
  return false;
4958
- return point.x >= bounds.x && point.x <= bounds.x + bounds.width &&
4959
- point.y >= bounds.y && point.y <= bounds.y + bounds.height;
5032
+ return point.x >= this._renderedPosition.x &&
5033
+ point.x <= this._renderedPosition.x + this._size.width &&
5034
+ point.y >= this._renderedPosition.y &&
5035
+ point.y <= this._renderedPosition.y + this._size.height;
4960
5036
  }
4961
5037
  /**
4962
5038
  * Trigger a reflow of text in this text box.
@@ -5041,6 +5117,41 @@ function getVerticalPadding(padding) {
5041
5117
  return padding.top + padding.bottom;
5042
5118
  }
5043
5119
 
5120
+ /**
5121
+ * Logger - Centralized logging for PC Editor.
5122
+ *
5123
+ * When enabled, logs informational messages to the console.
5124
+ * When disabled, only errors are logged.
5125
+ * Controlled via EditorOptions.enableLogging or Logger.setEnabled().
5126
+ */
5127
+ let _enabled = false;
5128
+ const Logger = {
5129
+ /** Enable or disable logging. When disabled, only errors are logged. */
5130
+ setEnabled(enabled) {
5131
+ _enabled = enabled;
5132
+ },
5133
+ /** Check if logging is enabled. */
5134
+ isEnabled() {
5135
+ return _enabled;
5136
+ },
5137
+ /** Log an informational message. Only outputs when logging is enabled. */
5138
+ log(...args) {
5139
+ if (_enabled) {
5140
+ console.log(...args);
5141
+ }
5142
+ },
5143
+ /** Log a warning. Only outputs when logging is enabled. */
5144
+ warn(...args) {
5145
+ if (_enabled) {
5146
+ console.warn(...args);
5147
+ }
5148
+ },
5149
+ /** Log an error. Always outputs regardless of logging state. */
5150
+ error(...args) {
5151
+ console.error(...args);
5152
+ }
5153
+ };
5154
+
5044
5155
  /**
5045
5156
  * TableCell - A cell within a table that contains editable text.
5046
5157
  * Implements EditableTextRegion for unified text interaction.
@@ -5091,7 +5202,7 @@ class TableCell extends EventEmitter {
5091
5202
  });
5092
5203
  // Prevent embedded objects in table cells (only substitution fields allowed)
5093
5204
  this._flowingContent.insertEmbeddedObject = () => {
5094
- console.warn('Embedded objects are not allowed in table cells. Use insertSubstitutionField instead.');
5205
+ Logger.warn('[pc-editor:TableCell] Embedded objects are not allowed in table cells. Use insertSubstitutionField instead.');
5095
5206
  };
5096
5207
  // Set initial content
5097
5208
  if (config.content) {
@@ -5408,7 +5519,7 @@ class TableCell extends EventEmitter {
5408
5519
  this._reflowDirty = false;
5409
5520
  this._lastReflowWidth = width;
5410
5521
  this._cachedContentHeight = null; // Clear cached height since lines changed
5411
- console.log('[TableCell.reflow] cellId:', this._id, 'text:', JSON.stringify(this._flowingContent.getText()), 'lines:', this._flowedLines.length);
5522
+ Logger.log('[pc-editor:TableCell.reflow] cellId:', this._id, 'text:', JSON.stringify(this._flowingContent.getText()), 'lines:', this._flowedLines.length);
5412
5523
  }
5413
5524
  /**
5414
5525
  * Mark this cell as needing reflow.
@@ -5446,7 +5557,7 @@ class TableCell extends EventEmitter {
5446
5557
  return this._editing && this._flowingContent.hasFocus();
5447
5558
  }
5448
5559
  handleKeyDown(e) {
5449
- console.log('[TableCell.handleKeyDown] Key:', e.key, '_editing:', this._editing, 'flowingContent.hasFocus:', this._flowingContent.hasFocus());
5560
+ Logger.log('[pc-editor:TableCell.handleKeyDown] Key:', e.key, '_editing:', this._editing, 'flowingContent.hasFocus:', this._flowingContent.hasFocus());
5450
5561
  if (!this._editing)
5451
5562
  return false;
5452
5563
  // Let parent table handle Tab navigation
@@ -5454,9 +5565,9 @@ class TableCell extends EventEmitter {
5454
5565
  return false; // Not handled - parent will handle
5455
5566
  }
5456
5567
  // Delegate to FlowingTextContent
5457
- console.log('[TableCell.handleKeyDown] Delegating to FlowingTextContent.handleKeyDown');
5568
+ Logger.log('[pc-editor:TableCell.handleKeyDown] Delegating to FlowingTextContent.handleKeyDown');
5458
5569
  const handled = this._flowingContent.handleKeyDown(e);
5459
- console.log('[TableCell.handleKeyDown] FlowingTextContent handled:', handled);
5570
+ Logger.log('[pc-editor:TableCell.handleKeyDown] FlowingTextContent handled:', handled);
5460
5571
  return handled;
5461
5572
  }
5462
5573
  onCursorBlink(handler) {
@@ -5528,28 +5639,49 @@ class TableCell extends EventEmitter {
5528
5639
  // Serialization
5529
5640
  // ============================================
5530
5641
  toData() {
5531
- const formattingMap = this._flowingContent.getFormattingManager().getAllFormatting();
5532
- const formattingRuns = [];
5533
- formattingMap.forEach((style, index) => {
5534
- formattingRuns.push([index, { ...style }]);
5535
- });
5642
+ const text = this._flowingContent.getText();
5643
+ const compressedRuns = this._flowingContent.getFormattingManager().getCompressedRuns(text.length);
5644
+ const formattingRuns = compressedRuns.map(run => [run.index, run.formatting]);
5536
5645
  // Get substitution fields for serialization
5537
5646
  const fields = this._flowingContent.getSubstitutionFieldManager().getFieldsArray();
5538
- return {
5539
- id: this._id,
5540
- rowSpan: this._rowSpan,
5541
- colSpan: this._colSpan,
5542
- backgroundColor: this._backgroundColor,
5543
- border: this._border,
5544
- padding: this._padding,
5545
- verticalAlign: this._verticalAlign,
5546
- content: this._flowingContent.getText(),
5547
- fontFamily: this._fontFamily,
5548
- fontSize: this._fontSize,
5549
- color: this._color,
5550
- formattingRuns: formattingRuns.length > 0 ? formattingRuns : undefined,
5551
- substitutionFields: fields.length > 0 ? fields : undefined
5552
- };
5647
+ // Only include non-default values to minimize export size
5648
+ const defaults = DEFAULT_TABLE_STYLE;
5649
+ const defaultBorder = DEFAULT_CELL_BORDER_SIDE;
5650
+ const isDefaultBorderSide = (side) => side.width === defaultBorder.width && side.color === defaultBorder.color && side.style === defaultBorder.style;
5651
+ const isDefaultBorder = isDefaultBorderSide(this._border.top) &&
5652
+ isDefaultBorderSide(this._border.right) &&
5653
+ isDefaultBorderSide(this._border.bottom) &&
5654
+ isDefaultBorderSide(this._border.left);
5655
+ const isDefaultPadding = this._padding.top === defaults.cellPadding &&
5656
+ this._padding.right === defaults.cellPadding &&
5657
+ this._padding.bottom === defaults.cellPadding &&
5658
+ this._padding.left === defaults.cellPadding;
5659
+ const data = {};
5660
+ if (this._rowSpan !== 1)
5661
+ data.rowSpan = this._rowSpan;
5662
+ if (this._colSpan !== 1)
5663
+ data.colSpan = this._colSpan;
5664
+ if (this._backgroundColor !== defaults.backgroundColor)
5665
+ data.backgroundColor = this._backgroundColor;
5666
+ if (!isDefaultBorder)
5667
+ data.border = this._border;
5668
+ if (!isDefaultPadding)
5669
+ data.padding = this._padding;
5670
+ if (this._verticalAlign !== 'top')
5671
+ data.verticalAlign = this._verticalAlign;
5672
+ if (text)
5673
+ data.content = text;
5674
+ if (this._fontFamily !== defaults.fontFamily)
5675
+ data.fontFamily = this._fontFamily;
5676
+ if (this._fontSize !== defaults.fontSize)
5677
+ data.fontSize = this._fontSize;
5678
+ if (this._color !== defaults.color)
5679
+ data.color = this._color;
5680
+ if (formattingRuns.length > 0)
5681
+ data.formattingRuns = formattingRuns;
5682
+ if (fields.length > 0)
5683
+ data.substitutionFields = fields;
5684
+ return data;
5553
5685
  }
5554
5686
  static fromData(data) {
5555
5687
  const cell = new TableCell({
@@ -5565,14 +5697,19 @@ class TableCell extends EventEmitter {
5565
5697
  fontSize: data.fontSize,
5566
5698
  color: data.color
5567
5699
  });
5568
- // Restore formatting runs
5569
- if (data.formattingRuns) {
5700
+ // Restore formatting runs (run-based: each entry applies from its index to the next)
5701
+ if (data.formattingRuns && data.formattingRuns.length > 0) {
5570
5702
  const formattingManager = cell._flowingContent.getFormattingManager();
5571
- const formattingMap = new Map();
5572
- for (const [index, style] of data.formattingRuns) {
5573
- formattingMap.set(index, style);
5703
+ const textLength = (data.content || '').length;
5704
+ for (let i = 0; i < data.formattingRuns.length; i++) {
5705
+ const [startIndex, style] = data.formattingRuns[i];
5706
+ const nextIndex = i + 1 < data.formattingRuns.length
5707
+ ? data.formattingRuns[i + 1][0]
5708
+ : textLength;
5709
+ if (startIndex < nextIndex) {
5710
+ formattingManager.applyFormatting(startIndex, nextIndex, style);
5711
+ }
5574
5712
  }
5575
- formattingManager.setAllFormatting(formattingMap);
5576
5713
  }
5577
5714
  // Restore substitution fields
5578
5715
  if (data.substitutionFields && Array.isArray(data.substitutionFields)) {
@@ -5806,13 +5943,17 @@ class TableRow extends EventEmitter {
5806
5943
  // Serialization
5807
5944
  // ============================================
5808
5945
  toData() {
5809
- return {
5810
- id: this._id,
5811
- height: this._height,
5812
- minHeight: this._minHeight,
5813
- isHeader: this._isHeader,
5946
+ const data = {
5814
5947
  cells: this._cells.map(cell => cell.toData())
5815
5948
  };
5949
+ // Only include non-default values
5950
+ if (this._height !== null)
5951
+ data.height = this._height;
5952
+ if (this._minHeight !== DEFAULT_TABLE_STYLE.minRowHeight)
5953
+ data.minHeight = this._minHeight;
5954
+ if (this._isHeader)
5955
+ data.isHeader = this._isHeader;
5956
+ return data;
5816
5957
  }
5817
5958
  static fromData(data) {
5818
5959
  const row = new TableRow({
@@ -6146,7 +6287,7 @@ class TableObject extends BaseEmbeddedObject {
6146
6287
  set position(value) {
6147
6288
  // Tables only support block positioning - ignore any attempt to set other modes
6148
6289
  if (value !== 'block') {
6149
- console.warn(`Tables only support 'block' positioning. Ignoring attempt to set '${value}'.`);
6290
+ Logger.warn(`[pc-editor:TableObject] Tables only support 'block' positioning. Ignoring attempt to set '${value}'.`);
6150
6291
  }
6151
6292
  // Always set to block
6152
6293
  super.position = 'block';
@@ -6685,24 +6826,24 @@ class TableObject extends BaseEmbeddedObject {
6685
6826
  createRowLoop(startRowIndex, endRowIndex, fieldPath) {
6686
6827
  // Validate range
6687
6828
  if (startRowIndex < 0 || endRowIndex >= this._rows.length) {
6688
- console.warn('[TableObject.createRowLoop] Invalid row range');
6829
+ Logger.warn('[pc-editor:TableObject.createRowLoop] Invalid row range');
6689
6830
  return null;
6690
6831
  }
6691
6832
  if (startRowIndex > endRowIndex) {
6692
- console.warn('[TableObject.createRowLoop] Start index must be <= end index');
6833
+ Logger.warn('[pc-editor:TableObject.createRowLoop] Start index must be <= end index');
6693
6834
  return null;
6694
6835
  }
6695
6836
  // Check for overlap with existing loops
6696
6837
  for (const existingLoop of this._rowLoops.values()) {
6697
6838
  if (this.loopRangesOverlap(startRowIndex, endRowIndex, existingLoop.startRowIndex, existingLoop.endRowIndex)) {
6698
- console.warn('[TableObject.createRowLoop] Loop range overlaps with existing loop');
6839
+ Logger.warn('[pc-editor:TableObject.createRowLoop] Loop range overlaps with existing loop');
6699
6840
  return null;
6700
6841
  }
6701
6842
  }
6702
6843
  // Check that loop rows are not header rows
6703
6844
  for (let i = startRowIndex; i <= endRowIndex; i++) {
6704
6845
  if (this._rows[i]?.isHeader) {
6705
- console.warn('[TableObject.createRowLoop] Loop rows cannot be header rows');
6846
+ Logger.warn('[pc-editor:TableObject.createRowLoop] Loop rows cannot be header rows');
6706
6847
  return null;
6707
6848
  }
6708
6849
  }
@@ -7508,7 +7649,7 @@ class TableObject extends BaseEmbeddedObject {
7508
7649
  return this._editing;
7509
7650
  }
7510
7651
  handleKeyDown(e) {
7511
- console.log('[TableObject.handleKeyDown] Key:', e.key, '_editing:', this._editing, '_focusedCell:', this._focusedCell);
7652
+ Logger.log('[pc-editor:TableObject.handleKeyDown] Key:', e.key, '_editing:', this._editing, '_focusedCell:', this._focusedCell);
7512
7653
  if (!this._editing)
7513
7654
  return false;
7514
7655
  // Handle Tab navigation
@@ -8296,14 +8437,20 @@ class EmbeddedObjectFactory {
8296
8437
  border: data.data.border,
8297
8438
  padding: data.data.padding
8298
8439
  });
8299
- // Restore formatting runs if present
8440
+ // Restore formatting runs (run-based: each entry applies from its index to the next)
8300
8441
  if (data.data.formattingRuns && Array.isArray(data.data.formattingRuns)) {
8301
- const formattingManager = textBox.flowingContent.getFormattingManager();
8302
- const formattingMap = new Map();
8303
- for (const [index, style] of data.data.formattingRuns) {
8304
- formattingMap.set(index, style);
8442
+ const runs = data.data.formattingRuns;
8443
+ if (runs.length > 0) {
8444
+ const formattingManager = textBox.flowingContent.getFormattingManager();
8445
+ const textLength = textBox.flowingContent.getText().length;
8446
+ for (let i = 0; i < runs.length; i++) {
8447
+ const [startIndex, style] = runs[i];
8448
+ const nextIndex = i + 1 < runs.length ? runs[i + 1][0] : textLength;
8449
+ if (startIndex < nextIndex) {
8450
+ formattingManager.applyFormatting(startIndex, nextIndex, style);
8451
+ }
8452
+ }
8305
8453
  }
8306
- formattingManager.setAllFormatting(formattingMap);
8307
8454
  }
8308
8455
  // Restore substitution fields if present
8309
8456
  if (data.data.substitutionFields && Array.isArray(data.data.substitutionFields)) {
@@ -9280,9 +9427,9 @@ class FlowingTextContent extends EventEmitter {
9280
9427
  * @returns true if the event was handled, false otherwise
9281
9428
  */
9282
9429
  handleKeyDown(e) {
9283
- console.log('[FlowingTextContent.handleKeyDown] Key:', e.key, '_hasFocus:', this._hasFocus);
9430
+ Logger.log('[pc-editor:FlowingTextContent.handleKeyDown] Key:', e.key, '_hasFocus:', this._hasFocus);
9284
9431
  if (!this._hasFocus) {
9285
- console.log('[FlowingTextContent.handleKeyDown] No focus, returning false');
9432
+ Logger.log('[pc-editor:FlowingTextContent.handleKeyDown] No focus, returning false');
9286
9433
  return false;
9287
9434
  }
9288
9435
  switch (e.key) {
@@ -9797,46 +9944,19 @@ class FlowingTextContent extends EventEmitter {
9797
9944
  toData() {
9798
9945
  // Serialize text content
9799
9946
  const text = this.textState.getText();
9800
- // Serialize text formatting as runs - only output when format changes
9801
- // This optimizes document size by not storing redundant formatting entries
9802
- const formattingRuns = [];
9803
- const defaultFormat = this.formatting.defaultFormatting;
9804
- let lastFormat = null;
9805
- for (let i = 0; i < text.length; i++) {
9806
- const currentFormat = this.formatting.getFormattingAt(i);
9807
- // Check if formatting changed from previous character
9808
- const formatChanged = lastFormat === null ||
9809
- currentFormat.fontFamily !== lastFormat.fontFamily ||
9810
- currentFormat.fontSize !== lastFormat.fontSize ||
9811
- currentFormat.fontWeight !== lastFormat.fontWeight ||
9812
- currentFormat.fontStyle !== lastFormat.fontStyle ||
9813
- currentFormat.color !== lastFormat.color ||
9814
- currentFormat.backgroundColor !== lastFormat.backgroundColor;
9815
- if (formatChanged) {
9816
- // Only output if different from default (to further reduce size)
9817
- const isDefault = currentFormat.fontFamily === defaultFormat.fontFamily &&
9818
- currentFormat.fontSize === defaultFormat.fontSize &&
9819
- currentFormat.fontWeight === defaultFormat.fontWeight &&
9820
- currentFormat.fontStyle === defaultFormat.fontStyle &&
9821
- currentFormat.color === defaultFormat.color &&
9822
- currentFormat.backgroundColor === defaultFormat.backgroundColor;
9823
- // Always output first run if it's not default, or output when format changes
9824
- if (!isDefault || formattingRuns.length > 0) {
9825
- formattingRuns.push({
9826
- index: i,
9827
- formatting: {
9828
- fontFamily: currentFormat.fontFamily,
9829
- fontSize: currentFormat.fontSize,
9830
- fontWeight: currentFormat.fontWeight,
9831
- fontStyle: currentFormat.fontStyle,
9832
- color: currentFormat.color,
9833
- backgroundColor: currentFormat.backgroundColor
9834
- }
9835
- });
9836
- }
9837
- lastFormat = currentFormat;
9947
+ // Serialize text formatting as compressed runs (only at change boundaries)
9948
+ const compressedRuns = this.formatting.getCompressedRuns(text.length);
9949
+ const formattingRuns = compressedRuns.map(run => ({
9950
+ index: run.index,
9951
+ formatting: {
9952
+ fontFamily: run.formatting.fontFamily,
9953
+ fontSize: run.formatting.fontSize,
9954
+ fontWeight: run.formatting.fontWeight,
9955
+ fontStyle: run.formatting.fontStyle,
9956
+ color: run.formatting.color,
9957
+ backgroundColor: run.formatting.backgroundColor
9838
9958
  }
9839
- }
9959
+ }));
9840
9960
  // Serialize paragraph formatting
9841
9961
  const paragraphFormatting = this.paragraphFormatting.toJSON();
9842
9962
  // Serialize substitution fields
@@ -9932,7 +10052,7 @@ class FlowingTextContent extends EventEmitter {
9932
10052
  content.getEmbeddedObjectManager().insert(object, ref.textIndex);
9933
10053
  }
9934
10054
  else {
9935
- console.warn(`Failed to create embedded object of type: ${ref.object.objectType}`);
10055
+ Logger.warn(`[pc-editor:FlowingTextContent] Failed to create embedded object of type: ${ref.object.objectType}`);
9936
10056
  }
9937
10057
  }
9938
10058
  }
@@ -9986,7 +10106,7 @@ class FlowingTextContent extends EventEmitter {
9986
10106
  this.embeddedObjects.insert(object, ref.textIndex);
9987
10107
  }
9988
10108
  else {
9989
- console.warn(`Failed to create embedded object of type: ${ref.object.objectType}`);
10109
+ Logger.warn(`[pc-editor:FlowingTextContent] Failed to create embedded object of type: ${ref.object.objectType}`);
9990
10110
  }
9991
10111
  }
9992
10112
  }
@@ -12525,7 +12645,7 @@ class FlowingTextRenderer extends EventEmitter {
12525
12645
  updateResizeHandleTargets(selectedObjects) {
12526
12646
  // Clear existing resize handle targets
12527
12647
  this._hitTestManager.clearCategory('resize-handles');
12528
- console.log('[updateResizeHandleTargets] selectedObjects:', selectedObjects.length);
12648
+ Logger.log('[pc-editor:FlowingTextRenderer] updateResizeHandleTargets selectedObjects:', selectedObjects.length);
12529
12649
  // Register resize handles for each selected object
12530
12650
  for (const object of selectedObjects) {
12531
12651
  if (!object.resizable)
@@ -12538,7 +12658,7 @@ class FlowingTextRenderer extends EventEmitter {
12538
12658
  // For regular objects, use renderedPosition
12539
12659
  const pos = object.renderedPosition;
12540
12660
  const pageIndex = object.renderedPageIndex;
12541
- console.log('[updateResizeHandleTargets] object:', object.id, 'pageIndex:', pageIndex, 'pos:', pos);
12661
+ Logger.log('[pc-editor:FlowingTextRenderer] updateResizeHandleTargets object:', object.id, 'pageIndex:', pageIndex, 'pos:', pos);
12542
12662
  if (pos && pageIndex >= 0) {
12543
12663
  this.registerObjectResizeHandles(object, pageIndex, pos);
12544
12664
  }
@@ -15184,7 +15304,7 @@ class CanvasManager extends EventEmitter {
15184
15304
  this.emit('element-removed', { elementId: objectId });
15185
15305
  }
15186
15306
  selectElement(elementId) {
15187
- console.log('Selecting element:', elementId);
15307
+ Logger.log('[pc-editor:CanvasManager] Selecting element:', elementId);
15188
15308
  this.selectedElements.add(elementId);
15189
15309
  // Update embedded object's selected state
15190
15310
  const flowingContents = [
@@ -15196,12 +15316,12 @@ class CanvasManager extends EventEmitter {
15196
15316
  const embeddedObjects = flowingContent.getEmbeddedObjects();
15197
15317
  for (const [, obj] of embeddedObjects.entries()) {
15198
15318
  if (obj.id === elementId) {
15199
- console.log('Found embedded object to select:', obj.id);
15319
+ Logger.log('[pc-editor:CanvasManager] Found embedded object to select:', obj.id);
15200
15320
  obj.selected = true;
15201
15321
  }
15202
15322
  }
15203
15323
  }
15204
- console.log('Selected elements after selection:', Array.from(this.selectedElements));
15324
+ Logger.log('[pc-editor:CanvasManager] Selected elements after selection:', Array.from(this.selectedElements));
15205
15325
  this.render();
15206
15326
  this.updateResizeHandleHitTargets();
15207
15327
  this.emit('selection-change', { selectedElements: Array.from(this.selectedElements) });
@@ -15251,10 +15371,10 @@ class CanvasManager extends EventEmitter {
15251
15371
  this.flowingTextRenderer.updateResizeHandleTargets(selectedObjects);
15252
15372
  }
15253
15373
  clearSelection() {
15254
- console.log('clearSelection called, current selected elements:', Array.from(this.selectedElements));
15374
+ Logger.log('[pc-editor:CanvasManager] clearSelection called, current selected elements:', Array.from(this.selectedElements));
15255
15375
  // Clear selected state on all embedded objects
15256
15376
  this.selectedElements.forEach(elementId => {
15257
- console.log('Clearing selection for element:', elementId);
15377
+ Logger.log('[pc-editor:CanvasManager] Clearing selection for element:', elementId);
15258
15378
  // Check embedded objects in all flowing content sources (body, header, footer)
15259
15379
  const flowingContents = [
15260
15380
  this.document.bodyFlowingContent,
@@ -15265,7 +15385,7 @@ class CanvasManager extends EventEmitter {
15265
15385
  const embeddedObjects = flowingContent.getEmbeddedObjects();
15266
15386
  for (const [, embeddedObj] of embeddedObjects.entries()) {
15267
15387
  if (embeddedObj.id === elementId) {
15268
- console.log('Clearing selection on embedded object:', elementId);
15388
+ Logger.log('[pc-editor:CanvasManager] Clearing selection on embedded object:', elementId);
15269
15389
  embeddedObj.selected = false;
15270
15390
  }
15271
15391
  }
@@ -15273,7 +15393,7 @@ class CanvasManager extends EventEmitter {
15273
15393
  });
15274
15394
  this.selectedElements.clear();
15275
15395
  this.selectedSectionId = null;
15276
- console.log('About to render after clearing selection...');
15396
+ Logger.log('[pc-editor:CanvasManager] About to render after clearing selection...');
15277
15397
  this.render();
15278
15398
  this.updateResizeHandleHitTargets();
15279
15399
  this.emit('selection-change', { selectedElements: [] });
@@ -15522,7 +15642,7 @@ class CanvasManager extends EventEmitter {
15522
15642
  });
15523
15643
  // Handle substitution field clicks
15524
15644
  this.flowingTextRenderer.on('substitution-field-clicked', (data) => {
15525
- console.log('[substitution-field-clicked] Field:', data.field?.fieldName, 'Section:', data.section);
15645
+ Logger.log('[pc-editor:CanvasManager] substitution-field-clicked Field:', data.field?.fieldName, 'Section:', data.section);
15526
15646
  // Emit event for external handling (e.g., showing field properties panel)
15527
15647
  this.emit('substitution-field-clicked', data);
15528
15648
  });
@@ -15997,14 +16117,15 @@ class CanvasManager extends EventEmitter {
15997
16117
  this.editingTextBox = textBox;
15998
16118
  this._editingTextBoxPageId = pageId || null;
15999
16119
  if (textBox) {
16000
- // Use the unified focus system to handle focus/blur and cursor blink
16001
- // This blurs the previous control, hiding its cursor
16002
- this.setFocus(textBox);
16003
16120
  // Clear selection in main flowing content
16004
16121
  this.document.bodyFlowingContent.clearSelection();
16005
- // Select the text box
16122
+ // Select the text box visually (this calls setFocus(null) internally,
16123
+ // so we must set focus to the text box AFTER this call)
16006
16124
  this.clearSelection();
16007
16125
  this.selectInlineElement({ type: 'embedded-object', object: textBox, textIndex: textBox.textIndex });
16126
+ // Now set focus to the text box for editing — must be AFTER selectInlineElement
16127
+ // because selectBaseEmbeddedObject calls setFocus(null) which would undo it
16128
+ this.setFocus(textBox);
16008
16129
  this.emit('textbox-editing-started', { textBox });
16009
16130
  }
16010
16131
  else {
@@ -16743,7 +16864,15 @@ class PDFGenerator {
16743
16864
  // Check if it's a data URL we can embed
16744
16865
  if (src.startsWith('data:')) {
16745
16866
  try {
16746
- const embeddedImage = await this.embedImageFromDataUrl(pdfDoc, src);
16867
+ let embeddedImage = await this.embedImageFromDataUrl(pdfDoc, src);
16868
+ // If the format isn't directly supported (e.g., SVG, WebP, GIF),
16869
+ // convert to PNG via canvas and try again
16870
+ if (!embeddedImage) {
16871
+ const pngDataUrl = image.toPngDataUrl();
16872
+ if (pngDataUrl) {
16873
+ embeddedImage = await this.embedImageFromDataUrl(pdfDoc, pngDataUrl);
16874
+ }
16875
+ }
16747
16876
  if (embeddedImage) {
16748
16877
  // Calculate draw position/size based on fit mode
16749
16878
  const drawParams = this.calculateImageDrawParams(embeddedImage.width, embeddedImage.height, image.width, image.height, image.fit);
@@ -16766,7 +16895,7 @@ class PDFGenerator {
16766
16895
  }
16767
16896
  }
16768
16897
  catch (e) {
16769
- console.warn('Failed to embed image:', e);
16898
+ Logger.warn('[pc-editor:PDFGenerator] Failed to embed image:', e);
16770
16899
  }
16771
16900
  }
16772
16901
  // Fallback: draw placeholder rectangle for images we can't embed
@@ -18794,7 +18923,7 @@ class MutationUndo {
18794
18923
  this.undoTableStructure(mutation);
18795
18924
  break;
18796
18925
  default:
18797
- console.warn('Unknown mutation type for undo:', mutation.type);
18926
+ Logger.warn('[pc-editor:MutationUndo] Unknown mutation type for undo:', mutation.type);
18798
18927
  }
18799
18928
  }
18800
18929
  /**
@@ -18844,7 +18973,7 @@ class MutationUndo {
18844
18973
  this.redoTableStructure(mutation);
18845
18974
  break;
18846
18975
  default:
18847
- console.warn('Unknown mutation type for redo:', mutation.type);
18976
+ Logger.warn('[pc-editor:MutationUndo] Unknown mutation type for redo:', mutation.type);
18848
18977
  }
18849
18978
  }
18850
18979
  // --- Text Mutations ---
@@ -20176,13 +20305,13 @@ class PDFParser {
20176
20305
  }
20177
20306
  catch {
20178
20307
  // Skip images that fail to extract
20179
- console.warn(`Failed to extract image: ${imageName}`);
20308
+ Logger.warn(`[pc-editor:PDFParser] Failed to extract image: ${imageName}`);
20180
20309
  }
20181
20310
  }
20182
20311
  }
20183
20312
  }
20184
20313
  catch (error) {
20185
- console.warn('Image extraction failed:', error);
20314
+ Logger.warn('[pc-editor:PDFParser] Image extraction failed:', error);
20186
20315
  }
20187
20316
  return images;
20188
20317
  }
@@ -21209,6 +21338,8 @@ class PCEditor extends EventEmitter {
21209
21338
  }
21210
21339
  this.container = container;
21211
21340
  this.options = this.mergeOptions(options);
21341
+ // Initialize logging
21342
+ Logger.setEnabled(this.options.enableLogging ?? false);
21212
21343
  this.document = new Document();
21213
21344
  // Apply constructor options to document settings
21214
21345
  this.document.updateSettings({
@@ -21233,7 +21364,8 @@ class PCEditor extends EventEmitter {
21233
21364
  showControlCharacters: options?.showControlCharacters ?? false,
21234
21365
  defaultFont: options?.defaultFont || 'Arial',
21235
21366
  defaultFontSize: options?.defaultFontSize || 12,
21236
- theme: options?.theme || 'light'
21367
+ theme: options?.theme || 'light',
21368
+ enableLogging: options?.enableLogging ?? false
21237
21369
  };
21238
21370
  }
21239
21371
  initialize() {
@@ -21640,6 +21772,7 @@ class PCEditor extends EventEmitter {
21640
21772
  * This changes which section receives keyboard input and cursor positioning.
21641
21773
  */
21642
21774
  setActiveSection(section) {
21775
+ Logger.log('[pc-editor] setActiveSection', section);
21643
21776
  if (this._activeEditingSection !== section) {
21644
21777
  this._activeEditingSection = section;
21645
21778
  // Delegate to canvas manager which handles the section change and emits events
@@ -21730,6 +21863,7 @@ class PCEditor extends EventEmitter {
21730
21863
  }
21731
21864
  }
21732
21865
  loadDocument(documentData) {
21866
+ Logger.log('[pc-editor] loadDocument');
21733
21867
  if (!this._isReady) {
21734
21868
  throw new Error('Editor is not ready');
21735
21869
  }
@@ -21756,6 +21890,7 @@ class PCEditor extends EventEmitter {
21756
21890
  return this.document.toData();
21757
21891
  }
21758
21892
  bindData(data) {
21893
+ Logger.log('[pc-editor] bindData');
21759
21894
  if (!this._isReady) {
21760
21895
  throw new Error('Editor is not ready');
21761
21896
  }
@@ -21764,6 +21899,7 @@ class PCEditor extends EventEmitter {
21764
21899
  this.emit('data-bound', { data });
21765
21900
  }
21766
21901
  async exportPDF(options) {
21902
+ Logger.log('[pc-editor] exportPDF');
21767
21903
  if (!this._isReady) {
21768
21904
  throw new Error('Editor is not ready');
21769
21905
  }
@@ -21805,6 +21941,7 @@ class PCEditor extends EventEmitter {
21805
21941
  * @returns JSON string representation of the document
21806
21942
  */
21807
21943
  saveDocument() {
21944
+ Logger.log('[pc-editor] saveDocument');
21808
21945
  if (!this._isReady) {
21809
21946
  throw new Error('Editor is not ready');
21810
21947
  }
@@ -21816,6 +21953,7 @@ class PCEditor extends EventEmitter {
21816
21953
  * @param filename Optional filename (defaults to 'document.pceditor.json')
21817
21954
  */
21818
21955
  saveDocumentToFile(filename = 'document.pceditor.json') {
21956
+ Logger.log('[pc-editor] saveDocumentToFile', filename);
21819
21957
  const jsonString = this.saveDocument();
21820
21958
  const blob = new Blob([jsonString], { type: 'application/json' });
21821
21959
  const url = URL.createObjectURL(blob);
@@ -21833,6 +21971,7 @@ class PCEditor extends EventEmitter {
21833
21971
  * @param jsonString JSON string representation of the document
21834
21972
  */
21835
21973
  loadDocumentFromJSON(jsonString) {
21974
+ Logger.log('[pc-editor] loadDocumentFromJSON');
21836
21975
  if (!this._isReady) {
21837
21976
  throw new Error('Editor is not ready');
21838
21977
  }
@@ -21853,6 +21992,7 @@ class PCEditor extends EventEmitter {
21853
21992
  * @returns Promise that resolves when loading is complete
21854
21993
  */
21855
21994
  async loadDocumentFromFile(file) {
21995
+ Logger.log('[pc-editor] loadDocumentFromFile', file.name);
21856
21996
  if (!this._isReady) {
21857
21997
  throw new Error('Editor is not ready');
21858
21998
  }
@@ -21941,22 +22081,25 @@ class PCEditor extends EventEmitter {
21941
22081
  // Version compatibility check
21942
22082
  const [major] = doc.version.split('.').map(Number);
21943
22083
  if (major > 1) {
21944
- console.warn(`Document version ${doc.version} may not be fully compatible with this editor`);
22084
+ Logger.warn(`[pc-editor] Document version ${doc.version} may not be fully compatible with this editor`);
21945
22085
  }
21946
22086
  }
21947
22087
  selectElement(elementId) {
22088
+ Logger.log('[pc-editor] selectElement', elementId);
21948
22089
  if (!this._isReady) {
21949
22090
  throw new Error('Editor is not ready');
21950
22091
  }
21951
22092
  this.canvasManager.selectElement(elementId);
21952
22093
  }
21953
22094
  clearSelection() {
22095
+ Logger.log('[pc-editor] clearSelection');
21954
22096
  if (!this._isReady) {
21955
22097
  throw new Error('Editor is not ready');
21956
22098
  }
21957
22099
  this.canvasManager.clearSelection();
21958
22100
  }
21959
22101
  removeEmbeddedObject(objectId) {
22102
+ Logger.log('[pc-editor] removeEmbeddedObject', objectId);
21960
22103
  if (!this._isReady) {
21961
22104
  throw new Error('Editor is not ready');
21962
22105
  }
@@ -21966,6 +22109,7 @@ class PCEditor extends EventEmitter {
21966
22109
  * Undo the last operation.
21967
22110
  */
21968
22111
  undo() {
22112
+ Logger.log('[pc-editor] undo');
21969
22113
  if (!this._isReady)
21970
22114
  return;
21971
22115
  const success = this.transactionManager.undo();
@@ -21978,6 +22122,7 @@ class PCEditor extends EventEmitter {
21978
22122
  * Redo the last undone operation.
21979
22123
  */
21980
22124
  redo() {
22125
+ Logger.log('[pc-editor] redo');
21981
22126
  if (!this._isReady)
21982
22127
  return;
21983
22128
  const success = this.transactionManager.redo();
@@ -22011,16 +22156,19 @@ class PCEditor extends EventEmitter {
22011
22156
  this.transactionManager.setMaxHistory(count);
22012
22157
  }
22013
22158
  zoomIn() {
22159
+ Logger.log('[pc-editor] zoomIn');
22014
22160
  if (!this._isReady)
22015
22161
  return;
22016
22162
  this.canvasManager.zoomIn();
22017
22163
  }
22018
22164
  zoomOut() {
22165
+ Logger.log('[pc-editor] zoomOut');
22019
22166
  if (!this._isReady)
22020
22167
  return;
22021
22168
  this.canvasManager.zoomOut();
22022
22169
  }
22023
22170
  setZoom(level) {
22171
+ Logger.log('[pc-editor] setZoom', level);
22024
22172
  if (!this._isReady)
22025
22173
  return;
22026
22174
  this.canvasManager.setZoom(level);
@@ -22057,6 +22205,7 @@ class PCEditor extends EventEmitter {
22057
22205
  return this.canvasManager.getContentOffset();
22058
22206
  }
22059
22207
  fitToWidth() {
22208
+ Logger.log('[pc-editor] fitToWidth');
22060
22209
  if (!this._isReady)
22061
22210
  return;
22062
22211
  this.canvasManager.fitToWidth();
@@ -22065,23 +22214,27 @@ class PCEditor extends EventEmitter {
22065
22214
  * Force a re-render of the canvas.
22066
22215
  */
22067
22216
  render() {
22217
+ Logger.log('[pc-editor] render');
22068
22218
  if (!this._isReady)
22069
22219
  return;
22070
22220
  this.canvasManager.render();
22071
22221
  }
22072
22222
  fitToPage() {
22223
+ Logger.log('[pc-editor] fitToPage');
22073
22224
  if (!this._isReady)
22074
22225
  return;
22075
22226
  this.canvasManager.fitToPage();
22076
22227
  }
22077
22228
  // Layout control methods
22078
22229
  setAutoFlow(enabled) {
22230
+ Logger.log('[pc-editor] setAutoFlow', enabled);
22079
22231
  if (!this._isReady) {
22080
22232
  throw new Error('Editor is not ready');
22081
22233
  }
22082
22234
  this.layoutEngine.setAutoFlow(enabled);
22083
22235
  }
22084
22236
  reflowDocument() {
22237
+ Logger.log('[pc-editor] reflowDocument');
22085
22238
  if (!this._isReady) {
22086
22239
  throw new Error('Editor is not ready');
22087
22240
  }
@@ -22123,6 +22276,7 @@ class PCEditor extends EventEmitter {
22123
22276
  };
22124
22277
  }
22125
22278
  addPage() {
22279
+ Logger.log('[pc-editor] addPage');
22126
22280
  if (!this._isReady) {
22127
22281
  throw new Error('Editor is not ready');
22128
22282
  }
@@ -22134,6 +22288,7 @@ class PCEditor extends EventEmitter {
22134
22288
  this.canvasManager.setDocument(this.document);
22135
22289
  }
22136
22290
  removePage(pageId) {
22291
+ Logger.log('[pc-editor] removePage', pageId);
22137
22292
  if (!this._isReady) {
22138
22293
  throw new Error('Editor is not ready');
22139
22294
  }
@@ -22208,7 +22363,7 @@ class PCEditor extends EventEmitter {
22208
22363
  }
22209
22364
  // Use the unified focus system to get the currently focused control
22210
22365
  const focusedControl = this.canvasManager.getFocusedControl();
22211
- console.log('[PCEditor.handleKeyDown] Key:', e.key, 'focusedControl:', focusedControl?.constructor?.name);
22366
+ Logger.log('[pc-editor:handleKeyDown] Key:', e.key, 'focusedControl:', focusedControl?.constructor?.name);
22212
22367
  if (!focusedControl)
22213
22368
  return;
22214
22369
  // Vertical navigation needs layout context - handle specially
@@ -22232,9 +22387,9 @@ class PCEditor extends EventEmitter {
22232
22387
  }
22233
22388
  }
22234
22389
  // Delegate to the focused control's handleKeyDown
22235
- console.log('[PCEditor.handleKeyDown] Calling focusedControl.handleKeyDown');
22390
+ Logger.log('[pc-editor:handleKeyDown] Calling focusedControl.handleKeyDown');
22236
22391
  const handled = focusedControl.handleKeyDown(e);
22237
- console.log('[PCEditor.handleKeyDown] handled:', handled);
22392
+ Logger.log('[pc-editor:handleKeyDown] handled:', handled);
22238
22393
  if (handled) {
22239
22394
  this.canvasManager.render();
22240
22395
  // Handle text box-specific post-processing
@@ -22598,6 +22753,7 @@ class PCEditor extends EventEmitter {
22598
22753
  * This is useful when UI controls have stolen focus.
22599
22754
  */
22600
22755
  applyFormattingWithFallback(start, end, formatting) {
22756
+ Logger.log('[pc-editor] applyFormattingWithFallback', start, end, formatting);
22601
22757
  // Try current context first
22602
22758
  let flowingContent = this.getEditingFlowingContent();
22603
22759
  // Fall back to saved context
@@ -22798,6 +22954,7 @@ class PCEditor extends EventEmitter {
22798
22954
  * Works for body text, text boxes, and table cells.
22799
22955
  */
22800
22956
  setUnifiedAlignment(alignment) {
22957
+ Logger.log('[pc-editor] setUnifiedAlignment', alignment);
22801
22958
  const flowingContent = this.getEditingFlowingContent();
22802
22959
  if (!flowingContent) {
22803
22960
  throw new Error('No text is being edited');
@@ -22814,6 +22971,7 @@ class PCEditor extends EventEmitter {
22814
22971
  this.canvasManager.render();
22815
22972
  }
22816
22973
  insertText(text) {
22974
+ Logger.log('[pc-editor] insertText', text);
22817
22975
  if (!this._isReady) {
22818
22976
  throw new Error('Editor is not ready');
22819
22977
  }
@@ -22830,6 +22988,7 @@ class PCEditor extends EventEmitter {
22830
22988
  return flowingContent ? flowingContent.getText() : '';
22831
22989
  }
22832
22990
  setFlowingText(text) {
22991
+ Logger.log('[pc-editor] setFlowingText');
22833
22992
  if (!this._isReady) {
22834
22993
  throw new Error('Editor is not ready');
22835
22994
  }
@@ -22843,6 +23002,7 @@ class PCEditor extends EventEmitter {
22843
23002
  * Works for body, header, footer, text boxes, and table cells.
22844
23003
  */
22845
23004
  setCursorPosition(position) {
23005
+ Logger.log('[pc-editor] setCursorPosition', position);
22846
23006
  if (!this._isReady) {
22847
23007
  throw new Error('Editor is not ready');
22848
23008
  }
@@ -22888,6 +23048,7 @@ class PCEditor extends EventEmitter {
22888
23048
  * Works for body, header, footer, text boxes, and table cells.
22889
23049
  */
22890
23050
  insertEmbeddedObject(object, position = 'inline') {
23051
+ Logger.log('[pc-editor] insertEmbeddedObject', object.id, position);
22891
23052
  if (!this._isReady) {
22892
23053
  throw new Error('Editor is not ready');
22893
23054
  }
@@ -22914,6 +23075,7 @@ class PCEditor extends EventEmitter {
22914
23075
  * Works for body, header, footer, text boxes, and table cells.
22915
23076
  */
22916
23077
  insertSubstitutionField(fieldName, config) {
23078
+ Logger.log('[pc-editor] insertSubstitutionField', fieldName);
22917
23079
  if (!this._isReady) {
22918
23080
  throw new Error('Editor is not ready');
22919
23081
  }
@@ -22939,6 +23101,7 @@ class PCEditor extends EventEmitter {
22939
23101
  * @param displayFormat Optional format string (e.g., "Page %d" where %d is replaced by page number)
22940
23102
  */
22941
23103
  insertPageNumberField(displayFormat) {
23104
+ Logger.log('[pc-editor] insertPageNumberField');
22942
23105
  if (!this._isReady) {
22943
23106
  throw new Error('Editor is not ready');
22944
23107
  }
@@ -22964,6 +23127,7 @@ class PCEditor extends EventEmitter {
22964
23127
  * @param displayFormat Optional format string (e.g., "of %d" where %d is replaced by page count)
22965
23128
  */
22966
23129
  insertPageCountField(displayFormat) {
23130
+ Logger.log('[pc-editor] insertPageCountField');
22967
23131
  if (!this._isReady) {
22968
23132
  throw new Error('Editor is not ready');
22969
23133
  }
@@ -22989,6 +23153,7 @@ class PCEditor extends EventEmitter {
22989
23153
  * or table cells are not recommended as these don't span pages.
22990
23154
  */
22991
23155
  insertPageBreak() {
23156
+ Logger.log('[pc-editor] insertPageBreak');
22992
23157
  if (!this._isReady) {
22993
23158
  throw new Error('Editor is not ready');
22994
23159
  }
@@ -23137,7 +23302,7 @@ class PCEditor extends EventEmitter {
23137
23302
  // Find the text box in all flowing contents
23138
23303
  const textBox = this.findTextBoxById(textBoxId);
23139
23304
  if (!textBox) {
23140
- console.warn(`[PCEditor.updateTextBox] Text box not found: ${textBoxId}`);
23305
+ Logger.warn(`[pc-editor:updateTextBox] Text box not found: ${textBoxId}`);
23141
23306
  return false;
23142
23307
  }
23143
23308
  // Apply updates
@@ -23200,7 +23365,7 @@ class PCEditor extends EventEmitter {
23200
23365
  // Find the image in all flowing contents
23201
23366
  const image = this.findImageById(imageId);
23202
23367
  if (!image) {
23203
- console.warn(`[PCEditor.updateImage] Image not found: ${imageId}`);
23368
+ Logger.warn(`[pc-editor:updateImage] Image not found: ${imageId}`);
23204
23369
  return false;
23205
23370
  }
23206
23371
  // Apply updates
@@ -23234,7 +23399,7 @@ class PCEditor extends EventEmitter {
23234
23399
  return false;
23235
23400
  const image = this.findImageById(imageId);
23236
23401
  if (!image) {
23237
- console.warn(`[PCEditor.setImageSource] Image not found: ${imageId}`);
23402
+ Logger.warn(`[pc-editor:setImageSource] Image not found: ${imageId}`);
23238
23403
  return false;
23239
23404
  }
23240
23405
  image.setSource(dataUrl, options);
@@ -23361,7 +23526,7 @@ class PCEditor extends EventEmitter {
23361
23526
  * @deprecated Use insertEmbeddedObject instead
23362
23527
  */
23363
23528
  insertInlineElement(_elementData, _position = 'inline') {
23364
- console.warn('insertInlineElement is deprecated and no longer functional. Use insertEmbeddedObject instead.');
23529
+ Logger.warn('[pc-editor] insertInlineElement is deprecated and no longer functional. Use insertEmbeddedObject instead.');
23365
23530
  }
23366
23531
  /**
23367
23532
  * Apply merge data to substitute all substitution fields with their values.
@@ -24086,18 +24251,37 @@ class PCEditor extends EventEmitter {
24086
24251
  return this.document.bodyFlowingContent.getParagraphBoundaries();
24087
24252
  }
24088
24253
  /**
24089
- * Create a repeating section in the body content.
24090
- * Note: Repeating sections are only supported in the body, not in header/footer.
24091
- * @param startIndex Text index at paragraph start (must be at a paragraph boundary)
24092
- * @param endIndex Text index at closing paragraph start (must be at a paragraph boundary)
24254
+ * Create a repeating section.
24255
+ *
24256
+ * If a table is currently being edited (focused), creates a table row loop
24257
+ * based on the focused cell's row. In this case, startIndex and endIndex
24258
+ * are ignored — the focused cell's row determines the loop range.
24259
+ *
24260
+ * Otherwise, creates a body text repeating section at the given paragraph boundaries.
24261
+ *
24262
+ * @param startIndex Text index at paragraph start (ignored for table row loops)
24263
+ * @param endIndex Text index at closing paragraph start (ignored for table row loops)
24093
24264
  * @param fieldPath The field path to the array to loop over (e.g., "items")
24094
- * @returns The created section, or null if boundaries are invalid
24265
+ * @returns The created section/loop, or null if creation failed
24095
24266
  */
24096
24267
  createRepeatingSection(startIndex, endIndex, fieldPath) {
24097
24268
  if (!this._isReady) {
24098
24269
  throw new Error('Editor is not ready');
24099
24270
  }
24271
+ // If a table is focused, create a row loop instead of a text repeating section
24272
+ const focusedTable = this.getFocusedTable();
24273
+ if (focusedTable && focusedTable.focusedCell) {
24274
+ Logger.log('[pc-editor] createRepeatingSection → table row loop', fieldPath);
24275
+ const row = focusedTable.focusedCell.row;
24276
+ const loop = focusedTable.createRowLoop(row, row, fieldPath);
24277
+ if (loop) {
24278
+ this.canvasManager.render();
24279
+ this.emit('table-row-loop-added', { table: focusedTable, loop });
24280
+ }
24281
+ return null; // Row loops are not RepeatingSections, return null
24282
+ }
24100
24283
  // Repeating sections only work in body (document-level)
24284
+ Logger.log('[pc-editor] createRepeatingSection', startIndex, endIndex, fieldPath);
24101
24285
  const section = this.document.bodyFlowingContent.createRepeatingSection(startIndex, endIndex, fieldPath);
24102
24286
  if (section) {
24103
24287
  this.canvasManager.render();
@@ -24475,7 +24659,7 @@ class PCEditor extends EventEmitter {
24475
24659
  createEmbeddedObjectFromData(data) {
24476
24660
  const object = EmbeddedObjectFactory.tryCreate(data);
24477
24661
  if (!object) {
24478
- console.warn('Unknown object type:', data.objectType);
24662
+ Logger.warn('[pc-editor] Unknown object type:', data.objectType);
24479
24663
  }
24480
24664
  return object;
24481
24665
  }
@@ -24504,6 +24688,13 @@ class PCEditor extends EventEmitter {
24504
24688
  return false;
24505
24689
  }
24506
24690
  }
24691
+ /**
24692
+ * Enable or disable verbose logging.
24693
+ * When disabled (default), only errors are logged to the console.
24694
+ */
24695
+ setLogging(enabled) {
24696
+ Logger.setEnabled(enabled);
24697
+ }
24507
24698
  destroy() {
24508
24699
  this.disableTextInput();
24509
24700
  if (this.canvasManager) {
@@ -25646,33 +25837,38 @@ class DocumentInfoPane extends BasePane {
25646
25837
  }
25647
25838
  createContent() {
25648
25839
  const container = document.createElement('div');
25649
- container.className = 'pc-pane-info-list';
25840
+ container.className = 'pc-pane-label-value-grid';
25650
25841
  // Page count
25651
- const countRow = this.createInfoRow('Pages', '0');
25652
- this.pageCountEl = countRow.querySelector('.pc-pane-info-value');
25653
- container.appendChild(countRow);
25842
+ container.appendChild(this.createLabel('Pages:'));
25843
+ this.pageCountEl = this.createValue('0');
25844
+ container.appendChild(this.pageCountEl);
25845
+ container.appendChild(this.createSpacer());
25654
25846
  // Page size
25655
- const sizeRow = this.createInfoRow('Size', '-');
25656
- this.pageSizeEl = sizeRow.querySelector('.pc-pane-info-value');
25657
- container.appendChild(sizeRow);
25847
+ container.appendChild(this.createLabel('Size:'));
25848
+ this.pageSizeEl = this.createValue('-');
25849
+ container.appendChild(this.pageSizeEl);
25850
+ container.appendChild(this.createSpacer());
25658
25851
  // Page orientation
25659
- const orientationRow = this.createInfoRow('Orientation', '-');
25660
- this.pageOrientationEl = orientationRow.querySelector('.pc-pane-info-value');
25661
- container.appendChild(orientationRow);
25852
+ container.appendChild(this.createLabel('Orientation:'));
25853
+ this.pageOrientationEl = this.createValue('-');
25854
+ container.appendChild(this.pageOrientationEl);
25855
+ container.appendChild(this.createSpacer());
25662
25856
  return container;
25663
25857
  }
25664
- createInfoRow(label, value) {
25665
- const row = document.createElement('div');
25666
- row.className = 'pc-pane-info';
25667
- const labelEl = document.createElement('span');
25668
- labelEl.className = 'pc-pane-info-label';
25669
- labelEl.textContent = label;
25670
- const valueEl = document.createElement('span');
25671
- valueEl.className = 'pc-pane-info-value';
25672
- valueEl.textContent = value;
25673
- row.appendChild(labelEl);
25674
- row.appendChild(valueEl);
25675
- return row;
25858
+ createLabel(text) {
25859
+ const label = document.createElement('span');
25860
+ label.className = 'pc-pane-label pc-pane-margin-label';
25861
+ label.textContent = text;
25862
+ return label;
25863
+ }
25864
+ createValue(text) {
25865
+ const value = document.createElement('span');
25866
+ value.className = 'pc-pane-info-value';
25867
+ value.textContent = text;
25868
+ return value;
25869
+ }
25870
+ createSpacer() {
25871
+ return document.createElement('div');
25676
25872
  }
25677
25873
  /**
25678
25874
  * Update the displayed information from the editor.
@@ -25852,24 +26048,48 @@ class DocumentSettingsPane extends BasePane {
25852
26048
  const container = document.createElement('div');
25853
26049
  // Margins section
25854
26050
  const marginsSection = this.createSection('Margins (mm)');
26051
+ // Five-column grid: label, edit, label, edit, stretch
25855
26052
  const marginsGrid = document.createElement('div');
25856
- marginsGrid.className = 'pc-pane-margins-grid';
26053
+ marginsGrid.className = 'pc-pane-margins-grid-5col';
25857
26054
  this.marginTopInput = this.createNumberInput({ min: 5, max: 50, step: 0.5, value: 20 });
25858
26055
  this.marginRightInput = this.createNumberInput({ min: 5, max: 50, step: 0.5, value: 20 });
25859
26056
  this.marginBottomInput = this.createNumberInput({ min: 5, max: 50, step: 0.5, value: 20 });
25860
26057
  this.marginLeftInput = this.createNumberInput({ min: 5, max: 50, step: 0.5, value: 20 });
25861
- marginsGrid.appendChild(this.createFormGroup('Top', this.marginTopInput, { inline: true }));
25862
- marginsGrid.appendChild(this.createFormGroup('Right', this.marginRightInput, { inline: true }));
25863
- marginsGrid.appendChild(this.createFormGroup('Bottom', this.marginBottomInput, { inline: true }));
25864
- marginsGrid.appendChild(this.createFormGroup('Left', this.marginLeftInput, { inline: true }));
26058
+ // Apply margins on blur
26059
+ const applyMargins = () => this.applyMargins();
26060
+ this.marginTopInput.addEventListener('blur', applyMargins);
26061
+ this.marginRightInput.addEventListener('blur', applyMargins);
26062
+ this.marginBottomInput.addEventListener('blur', applyMargins);
26063
+ this.marginLeftInput.addEventListener('blur', applyMargins);
26064
+ this.eventCleanup.push(() => {
26065
+ this.marginTopInput?.removeEventListener('blur', applyMargins);
26066
+ this.marginRightInput?.removeEventListener('blur', applyMargins);
26067
+ this.marginBottomInput?.removeEventListener('blur', applyMargins);
26068
+ this.marginLeftInput?.removeEventListener('blur', applyMargins);
26069
+ });
26070
+ // Row 1: Top / Right
26071
+ const topLabel = this.createMarginLabel('Top:');
26072
+ const rightLabel = this.createMarginLabel('Right:');
26073
+ marginsGrid.appendChild(topLabel);
26074
+ marginsGrid.appendChild(this.marginTopInput);
26075
+ marginsGrid.appendChild(rightLabel);
26076
+ marginsGrid.appendChild(this.marginRightInput);
26077
+ marginsGrid.appendChild(this.createSpacer());
26078
+ // Row 2: Bottom / Left
26079
+ const bottomLabel = this.createMarginLabel('Bottom:');
26080
+ const leftLabel = this.createMarginLabel('Left:');
26081
+ marginsGrid.appendChild(bottomLabel);
26082
+ marginsGrid.appendChild(this.marginBottomInput);
26083
+ marginsGrid.appendChild(leftLabel);
26084
+ marginsGrid.appendChild(this.marginLeftInput);
26085
+ marginsGrid.appendChild(this.createSpacer());
25865
26086
  marginsSection.appendChild(marginsGrid);
25866
- // Apply margins button
25867
- const applyMarginsBtn = this.createButton('Apply Margins');
25868
- this.addButtonListener(applyMarginsBtn, () => this.applyMargins());
25869
- marginsSection.appendChild(applyMarginsBtn);
25870
26087
  container.appendChild(marginsSection);
25871
- // Page size section
25872
- const pageSizeSection = this.createSection();
26088
+ // Page settings section using label-value grid: label, value, stretch
26089
+ const pageSection = this.createSection();
26090
+ const pageGrid = document.createElement('div');
26091
+ pageGrid.className = 'pc-pane-label-value-grid';
26092
+ // Page Size
25873
26093
  this.pageSizeSelect = this.createSelect([
25874
26094
  { value: 'A4', label: 'A4' },
25875
26095
  { value: 'Letter', label: 'Letter' },
@@ -25877,19 +26097,32 @@ class DocumentSettingsPane extends BasePane {
25877
26097
  { value: 'A3', label: 'A3' }
25878
26098
  ], 'A4');
25879
26099
  this.addImmediateApplyListener(this.pageSizeSelect, () => this.applyPageSettings());
25880
- pageSizeSection.appendChild(this.createFormGroup('Page Size', this.pageSizeSelect));
25881
- container.appendChild(pageSizeSection);
25882
- // Orientation section
25883
- const orientationSection = this.createSection();
26100
+ pageGrid.appendChild(this.createMarginLabel('Page Size:'));
26101
+ pageGrid.appendChild(this.pageSizeSelect);
26102
+ pageGrid.appendChild(this.createSpacer());
26103
+ // Orientation
25884
26104
  this.orientationSelect = this.createSelect([
25885
26105
  { value: 'portrait', label: 'Portrait' },
25886
26106
  { value: 'landscape', label: 'Landscape' }
25887
26107
  ], 'portrait');
25888
26108
  this.addImmediateApplyListener(this.orientationSelect, () => this.applyPageSettings());
25889
- orientationSection.appendChild(this.createFormGroup('Orientation', this.orientationSelect));
25890
- container.appendChild(orientationSection);
26109
+ pageGrid.appendChild(this.createMarginLabel('Orientation:'));
26110
+ pageGrid.appendChild(this.orientationSelect);
26111
+ pageGrid.appendChild(this.createSpacer());
26112
+ pageSection.appendChild(pageGrid);
26113
+ container.appendChild(pageSection);
25891
26114
  return container;
25892
26115
  }
26116
+ createMarginLabel(text) {
26117
+ const label = document.createElement('label');
26118
+ label.className = 'pc-pane-label pc-pane-margin-label';
26119
+ label.textContent = text;
26120
+ return label;
26121
+ }
26122
+ createSpacer() {
26123
+ const spacer = document.createElement('div');
26124
+ return spacer;
26125
+ }
25893
26126
  loadSettings() {
25894
26127
  if (!this.editor)
25895
26128
  return;
@@ -26429,6 +26662,7 @@ class HyperlinkPane extends BasePane {
26429
26662
  this.titleInput = null;
26430
26663
  this.rangeHint = null;
26431
26664
  this.currentHyperlink = null;
26665
+ this._isUpdating = false;
26432
26666
  this.onApply = options.onApply;
26433
26667
  this.onRemove = options.onRemove;
26434
26668
  }
@@ -26470,15 +26704,21 @@ class HyperlinkPane extends BasePane {
26470
26704
  return container;
26471
26705
  }
26472
26706
  updateFromCursor() {
26473
- if (!this.editor)
26707
+ if (!this.editor || this._isUpdating)
26474
26708
  return;
26475
- const cursorPos = this.editor.getCursorPosition();
26476
- const hyperlink = this.editor.getHyperlinkAt(cursorPos);
26477
- if (hyperlink) {
26478
- this.showHyperlink(hyperlink);
26709
+ this._isUpdating = true;
26710
+ try {
26711
+ const cursorPos = this.editor.getCursorPosition();
26712
+ const hyperlink = this.editor.getHyperlinkAt(cursorPos);
26713
+ if (hyperlink) {
26714
+ this.showHyperlink(hyperlink);
26715
+ }
26716
+ else {
26717
+ this.hideHyperlink();
26718
+ }
26479
26719
  }
26480
- else {
26481
- this.hideHyperlink();
26720
+ finally {
26721
+ this._isUpdating = false;
26482
26722
  }
26483
26723
  }
26484
26724
  showHyperlink(hyperlink) {
@@ -27097,6 +27337,7 @@ class TextBoxPane extends BasePane {
27097
27337
  this.borderColorInput = null;
27098
27338
  this.borderStyleSelect = null;
27099
27339
  this.paddingInput = null;
27340
+ this._isUpdating = false;
27100
27341
  this.currentTextBox = null;
27101
27342
  this.onApplyCallback = options.onApply;
27102
27343
  }
@@ -27170,14 +27411,20 @@ class TextBoxPane extends BasePane {
27170
27411
  return container;
27171
27412
  }
27172
27413
  updateFromSelection() {
27173
- if (!this.editor)
27414
+ if (!this.editor || this._isUpdating)
27174
27415
  return;
27175
- const textBox = this.editor.getSelectedTextBox?.();
27176
- if (textBox && !textBox.editing) {
27177
- this.showTextBox(textBox);
27416
+ this._isUpdating = true;
27417
+ try {
27418
+ const textBox = this.editor.getSelectedTextBox?.();
27419
+ if (textBox && !textBox.editing) {
27420
+ this.showTextBox(textBox);
27421
+ }
27422
+ else {
27423
+ this.hideTextBox();
27424
+ }
27178
27425
  }
27179
- else {
27180
- this.hideTextBox();
27426
+ finally {
27427
+ this._isUpdating = false;
27181
27428
  }
27182
27429
  }
27183
27430
  /**
@@ -27331,6 +27578,7 @@ class ImagePane extends BasePane {
27331
27578
  this.altTextInput = null;
27332
27579
  this.fileInput = null;
27333
27580
  this.currentImage = null;
27581
+ this._isUpdating = false;
27334
27582
  this.maxImageWidth = options.maxImageWidth ?? 400;
27335
27583
  this.maxImageHeight = options.maxImageHeight ?? 400;
27336
27584
  this.onApplyCallback = options.onApply;
@@ -27412,14 +27660,20 @@ class ImagePane extends BasePane {
27412
27660
  return container;
27413
27661
  }
27414
27662
  updateFromSelection() {
27415
- if (!this.editor)
27663
+ if (!this.editor || this._isUpdating)
27416
27664
  return;
27417
- const image = this.editor.getSelectedImage?.();
27418
- if (image) {
27419
- this.showImage(image);
27665
+ this._isUpdating = true;
27666
+ try {
27667
+ const image = this.editor.getSelectedImage?.();
27668
+ if (image) {
27669
+ this.showImage(image);
27670
+ }
27671
+ else {
27672
+ this.hideImage();
27673
+ }
27420
27674
  }
27421
- else {
27422
- this.hideImage();
27675
+ finally {
27676
+ this._isUpdating = false;
27423
27677
  }
27424
27678
  }
27425
27679
  /**
@@ -27584,6 +27838,8 @@ class TablePane extends BasePane {
27584
27838
  // Default controls
27585
27839
  this.defaultPaddingInput = null;
27586
27840
  this.defaultBorderColorInput = null;
27841
+ // Row loop controls
27842
+ this.loopFieldInput = null;
27587
27843
  // Cell formatting controls
27588
27844
  this.cellBgColorInput = null;
27589
27845
  this.borderTopCheck = null;
@@ -27594,6 +27850,7 @@ class TablePane extends BasePane {
27594
27850
  this.borderColorInput = null;
27595
27851
  this.borderStyleSelect = null;
27596
27852
  this.currentTable = null;
27853
+ this._isUpdating = false;
27597
27854
  this.onApplyCallback = options.onApply;
27598
27855
  }
27599
27856
  attach(options) {
@@ -27660,6 +27917,16 @@ class TablePane extends BasePane {
27660
27917
  this.addButtonListener(applyHeadersBtn, () => this.applyHeaders());
27661
27918
  headersSection.appendChild(applyHeadersBtn);
27662
27919
  container.appendChild(headersSection);
27920
+ // Row Loop section
27921
+ const loopSection = this.createSection('Row Loop');
27922
+ this.loopFieldInput = this.createTextInput({ placeholder: 'items' });
27923
+ loopSection.appendChild(this.createFormGroup('Array Field', this.loopFieldInput, {
27924
+ hint: 'Creates a loop on the currently focused row'
27925
+ }));
27926
+ const createLoopBtn = this.createButton('Create Row Loop');
27927
+ this.addButtonListener(createLoopBtn, () => this.createRowLoop());
27928
+ loopSection.appendChild(createLoopBtn);
27929
+ container.appendChild(loopSection);
27663
27930
  // Defaults section
27664
27931
  const defaultsSection = this.createSection('Defaults');
27665
27932
  const defaultsRow = this.createRow();
@@ -27732,14 +27999,20 @@ class TablePane extends BasePane {
27732
27999
  return container;
27733
28000
  }
27734
28001
  updateFromFocusedTable() {
27735
- if (!this.editor)
28002
+ if (!this.editor || this._isUpdating)
27736
28003
  return;
27737
- const table = this.editor.getFocusedTable();
27738
- if (table) {
27739
- this.showTable(table);
28004
+ this._isUpdating = true;
28005
+ try {
28006
+ const table = this.editor.getFocusedTable();
28007
+ if (table) {
28008
+ this.showTable(table);
28009
+ }
28010
+ else {
28011
+ this.hideTable();
28012
+ }
27740
28013
  }
27741
- else {
27742
- this.hideTable();
28014
+ finally {
28015
+ this._isUpdating = false;
27743
28016
  }
27744
28017
  }
27745
28018
  /**
@@ -27943,6 +28216,21 @@ class TablePane extends BasePane {
27943
28216
  hasTable() {
27944
28217
  return this.currentTable !== null;
27945
28218
  }
28219
+ createRowLoop() {
28220
+ if (!this.editor || !this.currentTable) {
28221
+ this.onApplyCallback?.(false, new Error('No table focused'));
28222
+ return;
28223
+ }
28224
+ const fieldPath = this.loopFieldInput?.value.trim() || '';
28225
+ if (!fieldPath) {
28226
+ this.onApplyCallback?.(false, new Error('Array field path is required'));
28227
+ return;
28228
+ }
28229
+ // Uses the unified createRepeatingSection API which detects
28230
+ // that a table is focused and creates a row loop on the focused row
28231
+ this.editor.createRepeatingSection(0, 0, fieldPath);
28232
+ this.onApplyCallback?.(true);
28233
+ }
27946
28234
  /**
27947
28235
  * Update the pane from current editor state.
27948
28236
  */
@@ -27975,6 +28263,7 @@ exports.HtmlConverter = HtmlConverter;
27975
28263
  exports.HyperlinkPane = HyperlinkPane;
27976
28264
  exports.ImageObject = ImageObject;
27977
28265
  exports.ImagePane = ImagePane;
28266
+ exports.Logger = Logger;
27978
28267
  exports.MergeDataPane = MergeDataPane;
27979
28268
  exports.PCEditor = PCEditor;
27980
28269
  exports.PDFImportError = PDFImportError;