@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
@@ -818,12 +818,13 @@ class TextFormattingManager extends EventEmitter {
818
818
  }
819
819
  /**
820
820
  * Get formatting at a specific character position.
821
- * Returns the position-specific formatting or the default.
821
+ * Returns the position-specific formatting merged with defaults,
822
+ * ensuring all properties are consistently present.
822
823
  */
823
824
  getFormattingAt(position) {
824
825
  const override = this.formatting.get(position);
825
826
  if (override) {
826
- return { ...override };
827
+ return { ...this._defaultFormatting, ...override };
827
828
  }
828
829
  return { ...this._defaultFormatting };
829
830
  }
@@ -910,6 +911,43 @@ class TextFormattingManager extends EventEmitter {
910
911
  getAllFormatting() {
911
912
  return new Map(this.formatting);
912
913
  }
914
+ /**
915
+ * Get formatting as compressed runs for serialization.
916
+ * Only outputs entries where formatting changes from the previous character.
917
+ * Skips leading default formatting to minimize output size.
918
+ * @param textLength Length of the text to serialize formatting for
919
+ */
920
+ getCompressedRuns(textLength) {
921
+ const runs = [];
922
+ const defaultFormat = this._defaultFormatting;
923
+ let lastFormat = null;
924
+ for (let i = 0; i < textLength; i++) {
925
+ const currentFormat = this.getFormattingAt(i);
926
+ const formatChanged = lastFormat === null ||
927
+ currentFormat.fontFamily !== lastFormat.fontFamily ||
928
+ currentFormat.fontSize !== lastFormat.fontSize ||
929
+ currentFormat.fontWeight !== lastFormat.fontWeight ||
930
+ currentFormat.fontStyle !== lastFormat.fontStyle ||
931
+ currentFormat.color !== lastFormat.color ||
932
+ currentFormat.backgroundColor !== lastFormat.backgroundColor;
933
+ if (formatChanged) {
934
+ const isDefault = currentFormat.fontFamily === defaultFormat.fontFamily &&
935
+ currentFormat.fontSize === defaultFormat.fontSize &&
936
+ currentFormat.fontWeight === defaultFormat.fontWeight &&
937
+ currentFormat.fontStyle === defaultFormat.fontStyle &&
938
+ currentFormat.color === defaultFormat.color &&
939
+ currentFormat.backgroundColor === defaultFormat.backgroundColor;
940
+ if (!isDefault || runs.length > 0) {
941
+ runs.push({
942
+ index: i,
943
+ formatting: { ...currentFormat }
944
+ });
945
+ }
946
+ lastFormat = currentFormat;
947
+ }
948
+ }
949
+ return runs;
950
+ }
913
951
  /**
914
952
  * Restore formatting from a map (for deserialization).
915
953
  */
@@ -3813,6 +3851,35 @@ class ImageObject extends BaseEmbeddedObject {
3813
3851
  get hasError() {
3814
3852
  return this._error;
3815
3853
  }
3854
+ /**
3855
+ * Get the loaded HTMLImageElement, if available.
3856
+ * Used by PDFGenerator to convert unsupported formats to PNG.
3857
+ */
3858
+ get imageElement() {
3859
+ return this._loaded ? this._image : null;
3860
+ }
3861
+ /**
3862
+ * Convert the image to a PNG data URL via canvas.
3863
+ * Used when the original format (e.g., SVG, WebP, GIF) is not supported by pdf-lib.
3864
+ * Returns null if the image is not loaded or conversion fails.
3865
+ */
3866
+ toPngDataUrl() {
3867
+ if (!this._loaded || !this._image)
3868
+ return null;
3869
+ try {
3870
+ const canvas = document.createElement('canvas');
3871
+ canvas.width = this._image.naturalWidth || this._size.width;
3872
+ canvas.height = this._image.naturalHeight || this._size.height;
3873
+ const ctx = canvas.getContext('2d');
3874
+ if (!ctx)
3875
+ return null;
3876
+ ctx.drawImage(this._image, 0, 0, canvas.width, canvas.height);
3877
+ return canvas.toDataURL('image/png');
3878
+ }
3879
+ catch {
3880
+ return null;
3881
+ }
3882
+ }
3816
3883
  loadImage() {
3817
3884
  if (!this._src) {
3818
3885
  this._error = true;
@@ -4708,12 +4775,10 @@ class TextBoxObject extends BaseEmbeddedObject {
4708
4775
  this.editing = false;
4709
4776
  }
4710
4777
  toData() {
4711
- // Serialize formatting map to array of [index, style] pairs
4712
- const formattingMap = this._flowingContent.getFormattingManager().getAllFormatting();
4713
- const formattingEntries = [];
4714
- formattingMap.forEach((value, key) => {
4715
- formattingEntries.push([key, { ...value }]);
4716
- });
4778
+ // Serialize formatting as compressed runs (only at change boundaries)
4779
+ const text = this._flowingContent.getText();
4780
+ const compressedRuns = this._flowingContent.getFormattingManager().getCompressedRuns(text.length);
4781
+ const formattingEntries = compressedRuns.map(run => [run.index, { ...run.formatting }]);
4717
4782
  // Get substitution fields as array
4718
4783
  const fields = this._flowingContent.getSubstitutionFieldManager().getFieldsArray();
4719
4784
  return {
@@ -4772,12 +4837,19 @@ class TextBoxObject extends BaseEmbeddedObject {
4772
4837
  this._border = { ...boxData.border };
4773
4838
  if (boxData.padding !== undefined)
4774
4839
  this._padding = boxData.padding;
4775
- // Restore formatting runs
4776
- if (boxData.formattingRuns) {
4840
+ // Restore formatting runs (run-based: each entry applies from its index to the next)
4841
+ if (boxData.formattingRuns && boxData.formattingRuns.length > 0) {
4777
4842
  const formattingManager = this._flowingContent.getFormattingManager();
4778
4843
  formattingManager.clear();
4779
- for (const [index, style] of boxData.formattingRuns) {
4780
- formattingManager.applyFormatting(index, index + 1, style);
4844
+ const textLength = this._flowingContent.getText().length;
4845
+ for (let i = 0; i < boxData.formattingRuns.length; i++) {
4846
+ const [startIndex, style] = boxData.formattingRuns[i];
4847
+ const nextIndex = i + 1 < boxData.formattingRuns.length
4848
+ ? boxData.formattingRuns[i + 1][0]
4849
+ : textLength;
4850
+ if (startIndex < nextIndex) {
4851
+ formattingManager.applyFormatting(startIndex, nextIndex, style);
4852
+ }
4781
4853
  }
4782
4854
  }
4783
4855
  // Restore substitution fields
@@ -4927,15 +4999,19 @@ class TextBoxObject extends BaseEmbeddedObject {
4927
4999
  }
4928
5000
  /**
4929
5001
  * Check if a point is within this text box region.
5002
+ * Uses the full object bounds (including padding/border) so that
5003
+ * double-click to enter edit mode works on the entire text box area,
5004
+ * not just the inner text area.
4930
5005
  * @param point Point in canvas coordinates
4931
- * @param pageIndex The page index (ignored for text boxes)
5006
+ * @param _pageIndex The page index (ignored for text boxes)
4932
5007
  */
4933
- containsPointInRegion(point, pageIndex) {
4934
- const bounds = this.getRegionBounds(pageIndex);
4935
- if (!bounds)
5008
+ containsPointInRegion(point, _pageIndex) {
5009
+ if (!this._renderedPosition)
4936
5010
  return false;
4937
- return point.x >= bounds.x && point.x <= bounds.x + bounds.width &&
4938
- point.y >= bounds.y && point.y <= bounds.y + bounds.height;
5011
+ return point.x >= this._renderedPosition.x &&
5012
+ point.x <= this._renderedPosition.x + this._size.width &&
5013
+ point.y >= this._renderedPosition.y &&
5014
+ point.y <= this._renderedPosition.y + this._size.height;
4939
5015
  }
4940
5016
  /**
4941
5017
  * Trigger a reflow of text in this text box.
@@ -5020,6 +5096,41 @@ function getVerticalPadding(padding) {
5020
5096
  return padding.top + padding.bottom;
5021
5097
  }
5022
5098
 
5099
+ /**
5100
+ * Logger - Centralized logging for PC Editor.
5101
+ *
5102
+ * When enabled, logs informational messages to the console.
5103
+ * When disabled, only errors are logged.
5104
+ * Controlled via EditorOptions.enableLogging or Logger.setEnabled().
5105
+ */
5106
+ let _enabled = false;
5107
+ const Logger = {
5108
+ /** Enable or disable logging. When disabled, only errors are logged. */
5109
+ setEnabled(enabled) {
5110
+ _enabled = enabled;
5111
+ },
5112
+ /** Check if logging is enabled. */
5113
+ isEnabled() {
5114
+ return _enabled;
5115
+ },
5116
+ /** Log an informational message. Only outputs when logging is enabled. */
5117
+ log(...args) {
5118
+ if (_enabled) {
5119
+ console.log(...args);
5120
+ }
5121
+ },
5122
+ /** Log a warning. Only outputs when logging is enabled. */
5123
+ warn(...args) {
5124
+ if (_enabled) {
5125
+ console.warn(...args);
5126
+ }
5127
+ },
5128
+ /** Log an error. Always outputs regardless of logging state. */
5129
+ error(...args) {
5130
+ console.error(...args);
5131
+ }
5132
+ };
5133
+
5023
5134
  /**
5024
5135
  * TableCell - A cell within a table that contains editable text.
5025
5136
  * Implements EditableTextRegion for unified text interaction.
@@ -5070,7 +5181,7 @@ class TableCell extends EventEmitter {
5070
5181
  });
5071
5182
  // Prevent embedded objects in table cells (only substitution fields allowed)
5072
5183
  this._flowingContent.insertEmbeddedObject = () => {
5073
- console.warn('Embedded objects are not allowed in table cells. Use insertSubstitutionField instead.');
5184
+ Logger.warn('[pc-editor:TableCell] Embedded objects are not allowed in table cells. Use insertSubstitutionField instead.');
5074
5185
  };
5075
5186
  // Set initial content
5076
5187
  if (config.content) {
@@ -5387,7 +5498,7 @@ class TableCell extends EventEmitter {
5387
5498
  this._reflowDirty = false;
5388
5499
  this._lastReflowWidth = width;
5389
5500
  this._cachedContentHeight = null; // Clear cached height since lines changed
5390
- console.log('[TableCell.reflow] cellId:', this._id, 'text:', JSON.stringify(this._flowingContent.getText()), 'lines:', this._flowedLines.length);
5501
+ Logger.log('[pc-editor:TableCell.reflow] cellId:', this._id, 'text:', JSON.stringify(this._flowingContent.getText()), 'lines:', this._flowedLines.length);
5391
5502
  }
5392
5503
  /**
5393
5504
  * Mark this cell as needing reflow.
@@ -5425,7 +5536,7 @@ class TableCell extends EventEmitter {
5425
5536
  return this._editing && this._flowingContent.hasFocus();
5426
5537
  }
5427
5538
  handleKeyDown(e) {
5428
- console.log('[TableCell.handleKeyDown] Key:', e.key, '_editing:', this._editing, 'flowingContent.hasFocus:', this._flowingContent.hasFocus());
5539
+ Logger.log('[pc-editor:TableCell.handleKeyDown] Key:', e.key, '_editing:', this._editing, 'flowingContent.hasFocus:', this._flowingContent.hasFocus());
5429
5540
  if (!this._editing)
5430
5541
  return false;
5431
5542
  // Let parent table handle Tab navigation
@@ -5433,9 +5544,9 @@ class TableCell extends EventEmitter {
5433
5544
  return false; // Not handled - parent will handle
5434
5545
  }
5435
5546
  // Delegate to FlowingTextContent
5436
- console.log('[TableCell.handleKeyDown] Delegating to FlowingTextContent.handleKeyDown');
5547
+ Logger.log('[pc-editor:TableCell.handleKeyDown] Delegating to FlowingTextContent.handleKeyDown');
5437
5548
  const handled = this._flowingContent.handleKeyDown(e);
5438
- console.log('[TableCell.handleKeyDown] FlowingTextContent handled:', handled);
5549
+ Logger.log('[pc-editor:TableCell.handleKeyDown] FlowingTextContent handled:', handled);
5439
5550
  return handled;
5440
5551
  }
5441
5552
  onCursorBlink(handler) {
@@ -5507,28 +5618,49 @@ class TableCell extends EventEmitter {
5507
5618
  // Serialization
5508
5619
  // ============================================
5509
5620
  toData() {
5510
- const formattingMap = this._flowingContent.getFormattingManager().getAllFormatting();
5511
- const formattingRuns = [];
5512
- formattingMap.forEach((style, index) => {
5513
- formattingRuns.push([index, { ...style }]);
5514
- });
5621
+ const text = this._flowingContent.getText();
5622
+ const compressedRuns = this._flowingContent.getFormattingManager().getCompressedRuns(text.length);
5623
+ const formattingRuns = compressedRuns.map(run => [run.index, run.formatting]);
5515
5624
  // Get substitution fields for serialization
5516
5625
  const fields = this._flowingContent.getSubstitutionFieldManager().getFieldsArray();
5517
- return {
5518
- id: this._id,
5519
- rowSpan: this._rowSpan,
5520
- colSpan: this._colSpan,
5521
- backgroundColor: this._backgroundColor,
5522
- border: this._border,
5523
- padding: this._padding,
5524
- verticalAlign: this._verticalAlign,
5525
- content: this._flowingContent.getText(),
5526
- fontFamily: this._fontFamily,
5527
- fontSize: this._fontSize,
5528
- color: this._color,
5529
- formattingRuns: formattingRuns.length > 0 ? formattingRuns : undefined,
5530
- substitutionFields: fields.length > 0 ? fields : undefined
5531
- };
5626
+ // Only include non-default values to minimize export size
5627
+ const defaults = DEFAULT_TABLE_STYLE;
5628
+ const defaultBorder = DEFAULT_CELL_BORDER_SIDE;
5629
+ const isDefaultBorderSide = (side) => side.width === defaultBorder.width && side.color === defaultBorder.color && side.style === defaultBorder.style;
5630
+ const isDefaultBorder = isDefaultBorderSide(this._border.top) &&
5631
+ isDefaultBorderSide(this._border.right) &&
5632
+ isDefaultBorderSide(this._border.bottom) &&
5633
+ isDefaultBorderSide(this._border.left);
5634
+ const isDefaultPadding = this._padding.top === defaults.cellPadding &&
5635
+ this._padding.right === defaults.cellPadding &&
5636
+ this._padding.bottom === defaults.cellPadding &&
5637
+ this._padding.left === defaults.cellPadding;
5638
+ const data = {};
5639
+ if (this._rowSpan !== 1)
5640
+ data.rowSpan = this._rowSpan;
5641
+ if (this._colSpan !== 1)
5642
+ data.colSpan = this._colSpan;
5643
+ if (this._backgroundColor !== defaults.backgroundColor)
5644
+ data.backgroundColor = this._backgroundColor;
5645
+ if (!isDefaultBorder)
5646
+ data.border = this._border;
5647
+ if (!isDefaultPadding)
5648
+ data.padding = this._padding;
5649
+ if (this._verticalAlign !== 'top')
5650
+ data.verticalAlign = this._verticalAlign;
5651
+ if (text)
5652
+ data.content = text;
5653
+ if (this._fontFamily !== defaults.fontFamily)
5654
+ data.fontFamily = this._fontFamily;
5655
+ if (this._fontSize !== defaults.fontSize)
5656
+ data.fontSize = this._fontSize;
5657
+ if (this._color !== defaults.color)
5658
+ data.color = this._color;
5659
+ if (formattingRuns.length > 0)
5660
+ data.formattingRuns = formattingRuns;
5661
+ if (fields.length > 0)
5662
+ data.substitutionFields = fields;
5663
+ return data;
5532
5664
  }
5533
5665
  static fromData(data) {
5534
5666
  const cell = new TableCell({
@@ -5544,14 +5676,19 @@ class TableCell extends EventEmitter {
5544
5676
  fontSize: data.fontSize,
5545
5677
  color: data.color
5546
5678
  });
5547
- // Restore formatting runs
5548
- if (data.formattingRuns) {
5679
+ // Restore formatting runs (run-based: each entry applies from its index to the next)
5680
+ if (data.formattingRuns && data.formattingRuns.length > 0) {
5549
5681
  const formattingManager = cell._flowingContent.getFormattingManager();
5550
- const formattingMap = new Map();
5551
- for (const [index, style] of data.formattingRuns) {
5552
- formattingMap.set(index, style);
5682
+ const textLength = (data.content || '').length;
5683
+ for (let i = 0; i < data.formattingRuns.length; i++) {
5684
+ const [startIndex, style] = data.formattingRuns[i];
5685
+ const nextIndex = i + 1 < data.formattingRuns.length
5686
+ ? data.formattingRuns[i + 1][0]
5687
+ : textLength;
5688
+ if (startIndex < nextIndex) {
5689
+ formattingManager.applyFormatting(startIndex, nextIndex, style);
5690
+ }
5553
5691
  }
5554
- formattingManager.setAllFormatting(formattingMap);
5555
5692
  }
5556
5693
  // Restore substitution fields
5557
5694
  if (data.substitutionFields && Array.isArray(data.substitutionFields)) {
@@ -5785,13 +5922,17 @@ class TableRow extends EventEmitter {
5785
5922
  // Serialization
5786
5923
  // ============================================
5787
5924
  toData() {
5788
- return {
5789
- id: this._id,
5790
- height: this._height,
5791
- minHeight: this._minHeight,
5792
- isHeader: this._isHeader,
5925
+ const data = {
5793
5926
  cells: this._cells.map(cell => cell.toData())
5794
5927
  };
5928
+ // Only include non-default values
5929
+ if (this._height !== null)
5930
+ data.height = this._height;
5931
+ if (this._minHeight !== DEFAULT_TABLE_STYLE.minRowHeight)
5932
+ data.minHeight = this._minHeight;
5933
+ if (this._isHeader)
5934
+ data.isHeader = this._isHeader;
5935
+ return data;
5795
5936
  }
5796
5937
  static fromData(data) {
5797
5938
  const row = new TableRow({
@@ -6125,7 +6266,7 @@ class TableObject extends BaseEmbeddedObject {
6125
6266
  set position(value) {
6126
6267
  // Tables only support block positioning - ignore any attempt to set other modes
6127
6268
  if (value !== 'block') {
6128
- console.warn(`Tables only support 'block' positioning. Ignoring attempt to set '${value}'.`);
6269
+ Logger.warn(`[pc-editor:TableObject] Tables only support 'block' positioning. Ignoring attempt to set '${value}'.`);
6129
6270
  }
6130
6271
  // Always set to block
6131
6272
  super.position = 'block';
@@ -6664,24 +6805,24 @@ class TableObject extends BaseEmbeddedObject {
6664
6805
  createRowLoop(startRowIndex, endRowIndex, fieldPath) {
6665
6806
  // Validate range
6666
6807
  if (startRowIndex < 0 || endRowIndex >= this._rows.length) {
6667
- console.warn('[TableObject.createRowLoop] Invalid row range');
6808
+ Logger.warn('[pc-editor:TableObject.createRowLoop] Invalid row range');
6668
6809
  return null;
6669
6810
  }
6670
6811
  if (startRowIndex > endRowIndex) {
6671
- console.warn('[TableObject.createRowLoop] Start index must be <= end index');
6812
+ Logger.warn('[pc-editor:TableObject.createRowLoop] Start index must be <= end index');
6672
6813
  return null;
6673
6814
  }
6674
6815
  // Check for overlap with existing loops
6675
6816
  for (const existingLoop of this._rowLoops.values()) {
6676
6817
  if (this.loopRangesOverlap(startRowIndex, endRowIndex, existingLoop.startRowIndex, existingLoop.endRowIndex)) {
6677
- console.warn('[TableObject.createRowLoop] Loop range overlaps with existing loop');
6818
+ Logger.warn('[pc-editor:TableObject.createRowLoop] Loop range overlaps with existing loop');
6678
6819
  return null;
6679
6820
  }
6680
6821
  }
6681
6822
  // Check that loop rows are not header rows
6682
6823
  for (let i = startRowIndex; i <= endRowIndex; i++) {
6683
6824
  if (this._rows[i]?.isHeader) {
6684
- console.warn('[TableObject.createRowLoop] Loop rows cannot be header rows');
6825
+ Logger.warn('[pc-editor:TableObject.createRowLoop] Loop rows cannot be header rows');
6685
6826
  return null;
6686
6827
  }
6687
6828
  }
@@ -7487,7 +7628,7 @@ class TableObject extends BaseEmbeddedObject {
7487
7628
  return this._editing;
7488
7629
  }
7489
7630
  handleKeyDown(e) {
7490
- console.log('[TableObject.handleKeyDown] Key:', e.key, '_editing:', this._editing, '_focusedCell:', this._focusedCell);
7631
+ Logger.log('[pc-editor:TableObject.handleKeyDown] Key:', e.key, '_editing:', this._editing, '_focusedCell:', this._focusedCell);
7491
7632
  if (!this._editing)
7492
7633
  return false;
7493
7634
  // Handle Tab navigation
@@ -8275,14 +8416,20 @@ class EmbeddedObjectFactory {
8275
8416
  border: data.data.border,
8276
8417
  padding: data.data.padding
8277
8418
  });
8278
- // Restore formatting runs if present
8419
+ // Restore formatting runs (run-based: each entry applies from its index to the next)
8279
8420
  if (data.data.formattingRuns && Array.isArray(data.data.formattingRuns)) {
8280
- const formattingManager = textBox.flowingContent.getFormattingManager();
8281
- const formattingMap = new Map();
8282
- for (const [index, style] of data.data.formattingRuns) {
8283
- formattingMap.set(index, style);
8421
+ const runs = data.data.formattingRuns;
8422
+ if (runs.length > 0) {
8423
+ const formattingManager = textBox.flowingContent.getFormattingManager();
8424
+ const textLength = textBox.flowingContent.getText().length;
8425
+ for (let i = 0; i < runs.length; i++) {
8426
+ const [startIndex, style] = runs[i];
8427
+ const nextIndex = i + 1 < runs.length ? runs[i + 1][0] : textLength;
8428
+ if (startIndex < nextIndex) {
8429
+ formattingManager.applyFormatting(startIndex, nextIndex, style);
8430
+ }
8431
+ }
8284
8432
  }
8285
- formattingManager.setAllFormatting(formattingMap);
8286
8433
  }
8287
8434
  // Restore substitution fields if present
8288
8435
  if (data.data.substitutionFields && Array.isArray(data.data.substitutionFields)) {
@@ -9259,9 +9406,9 @@ class FlowingTextContent extends EventEmitter {
9259
9406
  * @returns true if the event was handled, false otherwise
9260
9407
  */
9261
9408
  handleKeyDown(e) {
9262
- console.log('[FlowingTextContent.handleKeyDown] Key:', e.key, '_hasFocus:', this._hasFocus);
9409
+ Logger.log('[pc-editor:FlowingTextContent.handleKeyDown] Key:', e.key, '_hasFocus:', this._hasFocus);
9263
9410
  if (!this._hasFocus) {
9264
- console.log('[FlowingTextContent.handleKeyDown] No focus, returning false');
9411
+ Logger.log('[pc-editor:FlowingTextContent.handleKeyDown] No focus, returning false');
9265
9412
  return false;
9266
9413
  }
9267
9414
  switch (e.key) {
@@ -9776,46 +9923,19 @@ class FlowingTextContent extends EventEmitter {
9776
9923
  toData() {
9777
9924
  // Serialize text content
9778
9925
  const text = this.textState.getText();
9779
- // Serialize text formatting as runs - only output when format changes
9780
- // This optimizes document size by not storing redundant formatting entries
9781
- const formattingRuns = [];
9782
- const defaultFormat = this.formatting.defaultFormatting;
9783
- let lastFormat = null;
9784
- for (let i = 0; i < text.length; i++) {
9785
- const currentFormat = this.formatting.getFormattingAt(i);
9786
- // Check if formatting changed from previous character
9787
- const formatChanged = lastFormat === null ||
9788
- currentFormat.fontFamily !== lastFormat.fontFamily ||
9789
- currentFormat.fontSize !== lastFormat.fontSize ||
9790
- currentFormat.fontWeight !== lastFormat.fontWeight ||
9791
- currentFormat.fontStyle !== lastFormat.fontStyle ||
9792
- currentFormat.color !== lastFormat.color ||
9793
- currentFormat.backgroundColor !== lastFormat.backgroundColor;
9794
- if (formatChanged) {
9795
- // Only output if different from default (to further reduce size)
9796
- const isDefault = currentFormat.fontFamily === defaultFormat.fontFamily &&
9797
- currentFormat.fontSize === defaultFormat.fontSize &&
9798
- currentFormat.fontWeight === defaultFormat.fontWeight &&
9799
- currentFormat.fontStyle === defaultFormat.fontStyle &&
9800
- currentFormat.color === defaultFormat.color &&
9801
- currentFormat.backgroundColor === defaultFormat.backgroundColor;
9802
- // Always output first run if it's not default, or output when format changes
9803
- if (!isDefault || formattingRuns.length > 0) {
9804
- formattingRuns.push({
9805
- index: i,
9806
- formatting: {
9807
- fontFamily: currentFormat.fontFamily,
9808
- fontSize: currentFormat.fontSize,
9809
- fontWeight: currentFormat.fontWeight,
9810
- fontStyle: currentFormat.fontStyle,
9811
- color: currentFormat.color,
9812
- backgroundColor: currentFormat.backgroundColor
9813
- }
9814
- });
9815
- }
9816
- lastFormat = currentFormat;
9926
+ // Serialize text formatting as compressed runs (only at change boundaries)
9927
+ const compressedRuns = this.formatting.getCompressedRuns(text.length);
9928
+ const formattingRuns = compressedRuns.map(run => ({
9929
+ index: run.index,
9930
+ formatting: {
9931
+ fontFamily: run.formatting.fontFamily,
9932
+ fontSize: run.formatting.fontSize,
9933
+ fontWeight: run.formatting.fontWeight,
9934
+ fontStyle: run.formatting.fontStyle,
9935
+ color: run.formatting.color,
9936
+ backgroundColor: run.formatting.backgroundColor
9817
9937
  }
9818
- }
9938
+ }));
9819
9939
  // Serialize paragraph formatting
9820
9940
  const paragraphFormatting = this.paragraphFormatting.toJSON();
9821
9941
  // Serialize substitution fields
@@ -9911,7 +10031,7 @@ class FlowingTextContent extends EventEmitter {
9911
10031
  content.getEmbeddedObjectManager().insert(object, ref.textIndex);
9912
10032
  }
9913
10033
  else {
9914
- console.warn(`Failed to create embedded object of type: ${ref.object.objectType}`);
10034
+ Logger.warn(`[pc-editor:FlowingTextContent] Failed to create embedded object of type: ${ref.object.objectType}`);
9915
10035
  }
9916
10036
  }
9917
10037
  }
@@ -9965,7 +10085,7 @@ class FlowingTextContent extends EventEmitter {
9965
10085
  this.embeddedObjects.insert(object, ref.textIndex);
9966
10086
  }
9967
10087
  else {
9968
- console.warn(`Failed to create embedded object of type: ${ref.object.objectType}`);
10088
+ Logger.warn(`[pc-editor:FlowingTextContent] Failed to create embedded object of type: ${ref.object.objectType}`);
9969
10089
  }
9970
10090
  }
9971
10091
  }
@@ -12504,7 +12624,7 @@ class FlowingTextRenderer extends EventEmitter {
12504
12624
  updateResizeHandleTargets(selectedObjects) {
12505
12625
  // Clear existing resize handle targets
12506
12626
  this._hitTestManager.clearCategory('resize-handles');
12507
- console.log('[updateResizeHandleTargets] selectedObjects:', selectedObjects.length);
12627
+ Logger.log('[pc-editor:FlowingTextRenderer] updateResizeHandleTargets selectedObjects:', selectedObjects.length);
12508
12628
  // Register resize handles for each selected object
12509
12629
  for (const object of selectedObjects) {
12510
12630
  if (!object.resizable)
@@ -12517,7 +12637,7 @@ class FlowingTextRenderer extends EventEmitter {
12517
12637
  // For regular objects, use renderedPosition
12518
12638
  const pos = object.renderedPosition;
12519
12639
  const pageIndex = object.renderedPageIndex;
12520
- console.log('[updateResizeHandleTargets] object:', object.id, 'pageIndex:', pageIndex, 'pos:', pos);
12640
+ Logger.log('[pc-editor:FlowingTextRenderer] updateResizeHandleTargets object:', object.id, 'pageIndex:', pageIndex, 'pos:', pos);
12521
12641
  if (pos && pageIndex >= 0) {
12522
12642
  this.registerObjectResizeHandles(object, pageIndex, pos);
12523
12643
  }
@@ -15163,7 +15283,7 @@ class CanvasManager extends EventEmitter {
15163
15283
  this.emit('element-removed', { elementId: objectId });
15164
15284
  }
15165
15285
  selectElement(elementId) {
15166
- console.log('Selecting element:', elementId);
15286
+ Logger.log('[pc-editor:CanvasManager] Selecting element:', elementId);
15167
15287
  this.selectedElements.add(elementId);
15168
15288
  // Update embedded object's selected state
15169
15289
  const flowingContents = [
@@ -15175,12 +15295,12 @@ class CanvasManager extends EventEmitter {
15175
15295
  const embeddedObjects = flowingContent.getEmbeddedObjects();
15176
15296
  for (const [, obj] of embeddedObjects.entries()) {
15177
15297
  if (obj.id === elementId) {
15178
- console.log('Found embedded object to select:', obj.id);
15298
+ Logger.log('[pc-editor:CanvasManager] Found embedded object to select:', obj.id);
15179
15299
  obj.selected = true;
15180
15300
  }
15181
15301
  }
15182
15302
  }
15183
- console.log('Selected elements after selection:', Array.from(this.selectedElements));
15303
+ Logger.log('[pc-editor:CanvasManager] Selected elements after selection:', Array.from(this.selectedElements));
15184
15304
  this.render();
15185
15305
  this.updateResizeHandleHitTargets();
15186
15306
  this.emit('selection-change', { selectedElements: Array.from(this.selectedElements) });
@@ -15230,10 +15350,10 @@ class CanvasManager extends EventEmitter {
15230
15350
  this.flowingTextRenderer.updateResizeHandleTargets(selectedObjects);
15231
15351
  }
15232
15352
  clearSelection() {
15233
- console.log('clearSelection called, current selected elements:', Array.from(this.selectedElements));
15353
+ Logger.log('[pc-editor:CanvasManager] clearSelection called, current selected elements:', Array.from(this.selectedElements));
15234
15354
  // Clear selected state on all embedded objects
15235
15355
  this.selectedElements.forEach(elementId => {
15236
- console.log('Clearing selection for element:', elementId);
15356
+ Logger.log('[pc-editor:CanvasManager] Clearing selection for element:', elementId);
15237
15357
  // Check embedded objects in all flowing content sources (body, header, footer)
15238
15358
  const flowingContents = [
15239
15359
  this.document.bodyFlowingContent,
@@ -15244,7 +15364,7 @@ class CanvasManager extends EventEmitter {
15244
15364
  const embeddedObjects = flowingContent.getEmbeddedObjects();
15245
15365
  for (const [, embeddedObj] of embeddedObjects.entries()) {
15246
15366
  if (embeddedObj.id === elementId) {
15247
- console.log('Clearing selection on embedded object:', elementId);
15367
+ Logger.log('[pc-editor:CanvasManager] Clearing selection on embedded object:', elementId);
15248
15368
  embeddedObj.selected = false;
15249
15369
  }
15250
15370
  }
@@ -15252,7 +15372,7 @@ class CanvasManager extends EventEmitter {
15252
15372
  });
15253
15373
  this.selectedElements.clear();
15254
15374
  this.selectedSectionId = null;
15255
- console.log('About to render after clearing selection...');
15375
+ Logger.log('[pc-editor:CanvasManager] About to render after clearing selection...');
15256
15376
  this.render();
15257
15377
  this.updateResizeHandleHitTargets();
15258
15378
  this.emit('selection-change', { selectedElements: [] });
@@ -15501,7 +15621,7 @@ class CanvasManager extends EventEmitter {
15501
15621
  });
15502
15622
  // Handle substitution field clicks
15503
15623
  this.flowingTextRenderer.on('substitution-field-clicked', (data) => {
15504
- console.log('[substitution-field-clicked] Field:', data.field?.fieldName, 'Section:', data.section);
15624
+ Logger.log('[pc-editor:CanvasManager] substitution-field-clicked Field:', data.field?.fieldName, 'Section:', data.section);
15505
15625
  // Emit event for external handling (e.g., showing field properties panel)
15506
15626
  this.emit('substitution-field-clicked', data);
15507
15627
  });
@@ -15976,14 +16096,15 @@ class CanvasManager extends EventEmitter {
15976
16096
  this.editingTextBox = textBox;
15977
16097
  this._editingTextBoxPageId = pageId || null;
15978
16098
  if (textBox) {
15979
- // Use the unified focus system to handle focus/blur and cursor blink
15980
- // This blurs the previous control, hiding its cursor
15981
- this.setFocus(textBox);
15982
16099
  // Clear selection in main flowing content
15983
16100
  this.document.bodyFlowingContent.clearSelection();
15984
- // Select the text box
16101
+ // Select the text box visually (this calls setFocus(null) internally,
16102
+ // so we must set focus to the text box AFTER this call)
15985
16103
  this.clearSelection();
15986
16104
  this.selectInlineElement({ type: 'embedded-object', object: textBox, textIndex: textBox.textIndex });
16105
+ // Now set focus to the text box for editing — must be AFTER selectInlineElement
16106
+ // because selectBaseEmbeddedObject calls setFocus(null) which would undo it
16107
+ this.setFocus(textBox);
15987
16108
  this.emit('textbox-editing-started', { textBox });
15988
16109
  }
15989
16110
  else {
@@ -16722,7 +16843,15 @@ class PDFGenerator {
16722
16843
  // Check if it's a data URL we can embed
16723
16844
  if (src.startsWith('data:')) {
16724
16845
  try {
16725
- const embeddedImage = await this.embedImageFromDataUrl(pdfDoc, src);
16846
+ let embeddedImage = await this.embedImageFromDataUrl(pdfDoc, src);
16847
+ // If the format isn't directly supported (e.g., SVG, WebP, GIF),
16848
+ // convert to PNG via canvas and try again
16849
+ if (!embeddedImage) {
16850
+ const pngDataUrl = image.toPngDataUrl();
16851
+ if (pngDataUrl) {
16852
+ embeddedImage = await this.embedImageFromDataUrl(pdfDoc, pngDataUrl);
16853
+ }
16854
+ }
16726
16855
  if (embeddedImage) {
16727
16856
  // Calculate draw position/size based on fit mode
16728
16857
  const drawParams = this.calculateImageDrawParams(embeddedImage.width, embeddedImage.height, image.width, image.height, image.fit);
@@ -16745,7 +16874,7 @@ class PDFGenerator {
16745
16874
  }
16746
16875
  }
16747
16876
  catch (e) {
16748
- console.warn('Failed to embed image:', e);
16877
+ Logger.warn('[pc-editor:PDFGenerator] Failed to embed image:', e);
16749
16878
  }
16750
16879
  }
16751
16880
  // Fallback: draw placeholder rectangle for images we can't embed
@@ -18773,7 +18902,7 @@ class MutationUndo {
18773
18902
  this.undoTableStructure(mutation);
18774
18903
  break;
18775
18904
  default:
18776
- console.warn('Unknown mutation type for undo:', mutation.type);
18905
+ Logger.warn('[pc-editor:MutationUndo] Unknown mutation type for undo:', mutation.type);
18777
18906
  }
18778
18907
  }
18779
18908
  /**
@@ -18823,7 +18952,7 @@ class MutationUndo {
18823
18952
  this.redoTableStructure(mutation);
18824
18953
  break;
18825
18954
  default:
18826
- console.warn('Unknown mutation type for redo:', mutation.type);
18955
+ Logger.warn('[pc-editor:MutationUndo] Unknown mutation type for redo:', mutation.type);
18827
18956
  }
18828
18957
  }
18829
18958
  // --- Text Mutations ---
@@ -20155,13 +20284,13 @@ class PDFParser {
20155
20284
  }
20156
20285
  catch {
20157
20286
  // Skip images that fail to extract
20158
- console.warn(`Failed to extract image: ${imageName}`);
20287
+ Logger.warn(`[pc-editor:PDFParser] Failed to extract image: ${imageName}`);
20159
20288
  }
20160
20289
  }
20161
20290
  }
20162
20291
  }
20163
20292
  catch (error) {
20164
- console.warn('Image extraction failed:', error);
20293
+ Logger.warn('[pc-editor:PDFParser] Image extraction failed:', error);
20165
20294
  }
20166
20295
  return images;
20167
20296
  }
@@ -21188,6 +21317,8 @@ class PCEditor extends EventEmitter {
21188
21317
  }
21189
21318
  this.container = container;
21190
21319
  this.options = this.mergeOptions(options);
21320
+ // Initialize logging
21321
+ Logger.setEnabled(this.options.enableLogging ?? false);
21191
21322
  this.document = new Document();
21192
21323
  // Apply constructor options to document settings
21193
21324
  this.document.updateSettings({
@@ -21212,7 +21343,8 @@ class PCEditor extends EventEmitter {
21212
21343
  showControlCharacters: options?.showControlCharacters ?? false,
21213
21344
  defaultFont: options?.defaultFont || 'Arial',
21214
21345
  defaultFontSize: options?.defaultFontSize || 12,
21215
- theme: options?.theme || 'light'
21346
+ theme: options?.theme || 'light',
21347
+ enableLogging: options?.enableLogging ?? false
21216
21348
  };
21217
21349
  }
21218
21350
  initialize() {
@@ -21619,6 +21751,7 @@ class PCEditor extends EventEmitter {
21619
21751
  * This changes which section receives keyboard input and cursor positioning.
21620
21752
  */
21621
21753
  setActiveSection(section) {
21754
+ Logger.log('[pc-editor] setActiveSection', section);
21622
21755
  if (this._activeEditingSection !== section) {
21623
21756
  this._activeEditingSection = section;
21624
21757
  // Delegate to canvas manager which handles the section change and emits events
@@ -21709,6 +21842,7 @@ class PCEditor extends EventEmitter {
21709
21842
  }
21710
21843
  }
21711
21844
  loadDocument(documentData) {
21845
+ Logger.log('[pc-editor] loadDocument');
21712
21846
  if (!this._isReady) {
21713
21847
  throw new Error('Editor is not ready');
21714
21848
  }
@@ -21735,6 +21869,7 @@ class PCEditor extends EventEmitter {
21735
21869
  return this.document.toData();
21736
21870
  }
21737
21871
  bindData(data) {
21872
+ Logger.log('[pc-editor] bindData');
21738
21873
  if (!this._isReady) {
21739
21874
  throw new Error('Editor is not ready');
21740
21875
  }
@@ -21743,6 +21878,7 @@ class PCEditor extends EventEmitter {
21743
21878
  this.emit('data-bound', { data });
21744
21879
  }
21745
21880
  async exportPDF(options) {
21881
+ Logger.log('[pc-editor] exportPDF');
21746
21882
  if (!this._isReady) {
21747
21883
  throw new Error('Editor is not ready');
21748
21884
  }
@@ -21784,6 +21920,7 @@ class PCEditor extends EventEmitter {
21784
21920
  * @returns JSON string representation of the document
21785
21921
  */
21786
21922
  saveDocument() {
21923
+ Logger.log('[pc-editor] saveDocument');
21787
21924
  if (!this._isReady) {
21788
21925
  throw new Error('Editor is not ready');
21789
21926
  }
@@ -21795,6 +21932,7 @@ class PCEditor extends EventEmitter {
21795
21932
  * @param filename Optional filename (defaults to 'document.pceditor.json')
21796
21933
  */
21797
21934
  saveDocumentToFile(filename = 'document.pceditor.json') {
21935
+ Logger.log('[pc-editor] saveDocumentToFile', filename);
21798
21936
  const jsonString = this.saveDocument();
21799
21937
  const blob = new Blob([jsonString], { type: 'application/json' });
21800
21938
  const url = URL.createObjectURL(blob);
@@ -21812,6 +21950,7 @@ class PCEditor extends EventEmitter {
21812
21950
  * @param jsonString JSON string representation of the document
21813
21951
  */
21814
21952
  loadDocumentFromJSON(jsonString) {
21953
+ Logger.log('[pc-editor] loadDocumentFromJSON');
21815
21954
  if (!this._isReady) {
21816
21955
  throw new Error('Editor is not ready');
21817
21956
  }
@@ -21832,6 +21971,7 @@ class PCEditor extends EventEmitter {
21832
21971
  * @returns Promise that resolves when loading is complete
21833
21972
  */
21834
21973
  async loadDocumentFromFile(file) {
21974
+ Logger.log('[pc-editor] loadDocumentFromFile', file.name);
21835
21975
  if (!this._isReady) {
21836
21976
  throw new Error('Editor is not ready');
21837
21977
  }
@@ -21920,22 +22060,25 @@ class PCEditor extends EventEmitter {
21920
22060
  // Version compatibility check
21921
22061
  const [major] = doc.version.split('.').map(Number);
21922
22062
  if (major > 1) {
21923
- console.warn(`Document version ${doc.version} may not be fully compatible with this editor`);
22063
+ Logger.warn(`[pc-editor] Document version ${doc.version} may not be fully compatible with this editor`);
21924
22064
  }
21925
22065
  }
21926
22066
  selectElement(elementId) {
22067
+ Logger.log('[pc-editor] selectElement', elementId);
21927
22068
  if (!this._isReady) {
21928
22069
  throw new Error('Editor is not ready');
21929
22070
  }
21930
22071
  this.canvasManager.selectElement(elementId);
21931
22072
  }
21932
22073
  clearSelection() {
22074
+ Logger.log('[pc-editor] clearSelection');
21933
22075
  if (!this._isReady) {
21934
22076
  throw new Error('Editor is not ready');
21935
22077
  }
21936
22078
  this.canvasManager.clearSelection();
21937
22079
  }
21938
22080
  removeEmbeddedObject(objectId) {
22081
+ Logger.log('[pc-editor] removeEmbeddedObject', objectId);
21939
22082
  if (!this._isReady) {
21940
22083
  throw new Error('Editor is not ready');
21941
22084
  }
@@ -21945,6 +22088,7 @@ class PCEditor extends EventEmitter {
21945
22088
  * Undo the last operation.
21946
22089
  */
21947
22090
  undo() {
22091
+ Logger.log('[pc-editor] undo');
21948
22092
  if (!this._isReady)
21949
22093
  return;
21950
22094
  const success = this.transactionManager.undo();
@@ -21957,6 +22101,7 @@ class PCEditor extends EventEmitter {
21957
22101
  * Redo the last undone operation.
21958
22102
  */
21959
22103
  redo() {
22104
+ Logger.log('[pc-editor] redo');
21960
22105
  if (!this._isReady)
21961
22106
  return;
21962
22107
  const success = this.transactionManager.redo();
@@ -21990,16 +22135,19 @@ class PCEditor extends EventEmitter {
21990
22135
  this.transactionManager.setMaxHistory(count);
21991
22136
  }
21992
22137
  zoomIn() {
22138
+ Logger.log('[pc-editor] zoomIn');
21993
22139
  if (!this._isReady)
21994
22140
  return;
21995
22141
  this.canvasManager.zoomIn();
21996
22142
  }
21997
22143
  zoomOut() {
22144
+ Logger.log('[pc-editor] zoomOut');
21998
22145
  if (!this._isReady)
21999
22146
  return;
22000
22147
  this.canvasManager.zoomOut();
22001
22148
  }
22002
22149
  setZoom(level) {
22150
+ Logger.log('[pc-editor] setZoom', level);
22003
22151
  if (!this._isReady)
22004
22152
  return;
22005
22153
  this.canvasManager.setZoom(level);
@@ -22036,6 +22184,7 @@ class PCEditor extends EventEmitter {
22036
22184
  return this.canvasManager.getContentOffset();
22037
22185
  }
22038
22186
  fitToWidth() {
22187
+ Logger.log('[pc-editor] fitToWidth');
22039
22188
  if (!this._isReady)
22040
22189
  return;
22041
22190
  this.canvasManager.fitToWidth();
@@ -22044,23 +22193,27 @@ class PCEditor extends EventEmitter {
22044
22193
  * Force a re-render of the canvas.
22045
22194
  */
22046
22195
  render() {
22196
+ Logger.log('[pc-editor] render');
22047
22197
  if (!this._isReady)
22048
22198
  return;
22049
22199
  this.canvasManager.render();
22050
22200
  }
22051
22201
  fitToPage() {
22202
+ Logger.log('[pc-editor] fitToPage');
22052
22203
  if (!this._isReady)
22053
22204
  return;
22054
22205
  this.canvasManager.fitToPage();
22055
22206
  }
22056
22207
  // Layout control methods
22057
22208
  setAutoFlow(enabled) {
22209
+ Logger.log('[pc-editor] setAutoFlow', enabled);
22058
22210
  if (!this._isReady) {
22059
22211
  throw new Error('Editor is not ready');
22060
22212
  }
22061
22213
  this.layoutEngine.setAutoFlow(enabled);
22062
22214
  }
22063
22215
  reflowDocument() {
22216
+ Logger.log('[pc-editor] reflowDocument');
22064
22217
  if (!this._isReady) {
22065
22218
  throw new Error('Editor is not ready');
22066
22219
  }
@@ -22102,6 +22255,7 @@ class PCEditor extends EventEmitter {
22102
22255
  };
22103
22256
  }
22104
22257
  addPage() {
22258
+ Logger.log('[pc-editor] addPage');
22105
22259
  if (!this._isReady) {
22106
22260
  throw new Error('Editor is not ready');
22107
22261
  }
@@ -22113,6 +22267,7 @@ class PCEditor extends EventEmitter {
22113
22267
  this.canvasManager.setDocument(this.document);
22114
22268
  }
22115
22269
  removePage(pageId) {
22270
+ Logger.log('[pc-editor] removePage', pageId);
22116
22271
  if (!this._isReady) {
22117
22272
  throw new Error('Editor is not ready');
22118
22273
  }
@@ -22187,7 +22342,7 @@ class PCEditor extends EventEmitter {
22187
22342
  }
22188
22343
  // Use the unified focus system to get the currently focused control
22189
22344
  const focusedControl = this.canvasManager.getFocusedControl();
22190
- console.log('[PCEditor.handleKeyDown] Key:', e.key, 'focusedControl:', focusedControl?.constructor?.name);
22345
+ Logger.log('[pc-editor:handleKeyDown] Key:', e.key, 'focusedControl:', focusedControl?.constructor?.name);
22191
22346
  if (!focusedControl)
22192
22347
  return;
22193
22348
  // Vertical navigation needs layout context - handle specially
@@ -22211,9 +22366,9 @@ class PCEditor extends EventEmitter {
22211
22366
  }
22212
22367
  }
22213
22368
  // Delegate to the focused control's handleKeyDown
22214
- console.log('[PCEditor.handleKeyDown] Calling focusedControl.handleKeyDown');
22369
+ Logger.log('[pc-editor:handleKeyDown] Calling focusedControl.handleKeyDown');
22215
22370
  const handled = focusedControl.handleKeyDown(e);
22216
- console.log('[PCEditor.handleKeyDown] handled:', handled);
22371
+ Logger.log('[pc-editor:handleKeyDown] handled:', handled);
22217
22372
  if (handled) {
22218
22373
  this.canvasManager.render();
22219
22374
  // Handle text box-specific post-processing
@@ -22577,6 +22732,7 @@ class PCEditor extends EventEmitter {
22577
22732
  * This is useful when UI controls have stolen focus.
22578
22733
  */
22579
22734
  applyFormattingWithFallback(start, end, formatting) {
22735
+ Logger.log('[pc-editor] applyFormattingWithFallback', start, end, formatting);
22580
22736
  // Try current context first
22581
22737
  let flowingContent = this.getEditingFlowingContent();
22582
22738
  // Fall back to saved context
@@ -22777,6 +22933,7 @@ class PCEditor extends EventEmitter {
22777
22933
  * Works for body text, text boxes, and table cells.
22778
22934
  */
22779
22935
  setUnifiedAlignment(alignment) {
22936
+ Logger.log('[pc-editor] setUnifiedAlignment', alignment);
22780
22937
  const flowingContent = this.getEditingFlowingContent();
22781
22938
  if (!flowingContent) {
22782
22939
  throw new Error('No text is being edited');
@@ -22793,6 +22950,7 @@ class PCEditor extends EventEmitter {
22793
22950
  this.canvasManager.render();
22794
22951
  }
22795
22952
  insertText(text) {
22953
+ Logger.log('[pc-editor] insertText', text);
22796
22954
  if (!this._isReady) {
22797
22955
  throw new Error('Editor is not ready');
22798
22956
  }
@@ -22809,6 +22967,7 @@ class PCEditor extends EventEmitter {
22809
22967
  return flowingContent ? flowingContent.getText() : '';
22810
22968
  }
22811
22969
  setFlowingText(text) {
22970
+ Logger.log('[pc-editor] setFlowingText');
22812
22971
  if (!this._isReady) {
22813
22972
  throw new Error('Editor is not ready');
22814
22973
  }
@@ -22822,6 +22981,7 @@ class PCEditor extends EventEmitter {
22822
22981
  * Works for body, header, footer, text boxes, and table cells.
22823
22982
  */
22824
22983
  setCursorPosition(position) {
22984
+ Logger.log('[pc-editor] setCursorPosition', position);
22825
22985
  if (!this._isReady) {
22826
22986
  throw new Error('Editor is not ready');
22827
22987
  }
@@ -22867,6 +23027,7 @@ class PCEditor extends EventEmitter {
22867
23027
  * Works for body, header, footer, text boxes, and table cells.
22868
23028
  */
22869
23029
  insertEmbeddedObject(object, position = 'inline') {
23030
+ Logger.log('[pc-editor] insertEmbeddedObject', object.id, position);
22870
23031
  if (!this._isReady) {
22871
23032
  throw new Error('Editor is not ready');
22872
23033
  }
@@ -22893,6 +23054,7 @@ class PCEditor extends EventEmitter {
22893
23054
  * Works for body, header, footer, text boxes, and table cells.
22894
23055
  */
22895
23056
  insertSubstitutionField(fieldName, config) {
23057
+ Logger.log('[pc-editor] insertSubstitutionField', fieldName);
22896
23058
  if (!this._isReady) {
22897
23059
  throw new Error('Editor is not ready');
22898
23060
  }
@@ -22918,6 +23080,7 @@ class PCEditor extends EventEmitter {
22918
23080
  * @param displayFormat Optional format string (e.g., "Page %d" where %d is replaced by page number)
22919
23081
  */
22920
23082
  insertPageNumberField(displayFormat) {
23083
+ Logger.log('[pc-editor] insertPageNumberField');
22921
23084
  if (!this._isReady) {
22922
23085
  throw new Error('Editor is not ready');
22923
23086
  }
@@ -22943,6 +23106,7 @@ class PCEditor extends EventEmitter {
22943
23106
  * @param displayFormat Optional format string (e.g., "of %d" where %d is replaced by page count)
22944
23107
  */
22945
23108
  insertPageCountField(displayFormat) {
23109
+ Logger.log('[pc-editor] insertPageCountField');
22946
23110
  if (!this._isReady) {
22947
23111
  throw new Error('Editor is not ready');
22948
23112
  }
@@ -22968,6 +23132,7 @@ class PCEditor extends EventEmitter {
22968
23132
  * or table cells are not recommended as these don't span pages.
22969
23133
  */
22970
23134
  insertPageBreak() {
23135
+ Logger.log('[pc-editor] insertPageBreak');
22971
23136
  if (!this._isReady) {
22972
23137
  throw new Error('Editor is not ready');
22973
23138
  }
@@ -23116,7 +23281,7 @@ class PCEditor extends EventEmitter {
23116
23281
  // Find the text box in all flowing contents
23117
23282
  const textBox = this.findTextBoxById(textBoxId);
23118
23283
  if (!textBox) {
23119
- console.warn(`[PCEditor.updateTextBox] Text box not found: ${textBoxId}`);
23284
+ Logger.warn(`[pc-editor:updateTextBox] Text box not found: ${textBoxId}`);
23120
23285
  return false;
23121
23286
  }
23122
23287
  // Apply updates
@@ -23179,7 +23344,7 @@ class PCEditor extends EventEmitter {
23179
23344
  // Find the image in all flowing contents
23180
23345
  const image = this.findImageById(imageId);
23181
23346
  if (!image) {
23182
- console.warn(`[PCEditor.updateImage] Image not found: ${imageId}`);
23347
+ Logger.warn(`[pc-editor:updateImage] Image not found: ${imageId}`);
23183
23348
  return false;
23184
23349
  }
23185
23350
  // Apply updates
@@ -23213,7 +23378,7 @@ class PCEditor extends EventEmitter {
23213
23378
  return false;
23214
23379
  const image = this.findImageById(imageId);
23215
23380
  if (!image) {
23216
- console.warn(`[PCEditor.setImageSource] Image not found: ${imageId}`);
23381
+ Logger.warn(`[pc-editor:setImageSource] Image not found: ${imageId}`);
23217
23382
  return false;
23218
23383
  }
23219
23384
  image.setSource(dataUrl, options);
@@ -23340,7 +23505,7 @@ class PCEditor extends EventEmitter {
23340
23505
  * @deprecated Use insertEmbeddedObject instead
23341
23506
  */
23342
23507
  insertInlineElement(_elementData, _position = 'inline') {
23343
- console.warn('insertInlineElement is deprecated and no longer functional. Use insertEmbeddedObject instead.');
23508
+ Logger.warn('[pc-editor] insertInlineElement is deprecated and no longer functional. Use insertEmbeddedObject instead.');
23344
23509
  }
23345
23510
  /**
23346
23511
  * Apply merge data to substitute all substitution fields with their values.
@@ -24065,18 +24230,37 @@ class PCEditor extends EventEmitter {
24065
24230
  return this.document.bodyFlowingContent.getParagraphBoundaries();
24066
24231
  }
24067
24232
  /**
24068
- * Create a repeating section in the body content.
24069
- * Note: Repeating sections are only supported in the body, not in header/footer.
24070
- * @param startIndex Text index at paragraph start (must be at a paragraph boundary)
24071
- * @param endIndex Text index at closing paragraph start (must be at a paragraph boundary)
24233
+ * Create a repeating section.
24234
+ *
24235
+ * If a table is currently being edited (focused), creates a table row loop
24236
+ * based on the focused cell's row. In this case, startIndex and endIndex
24237
+ * are ignored — the focused cell's row determines the loop range.
24238
+ *
24239
+ * Otherwise, creates a body text repeating section at the given paragraph boundaries.
24240
+ *
24241
+ * @param startIndex Text index at paragraph start (ignored for table row loops)
24242
+ * @param endIndex Text index at closing paragraph start (ignored for table row loops)
24072
24243
  * @param fieldPath The field path to the array to loop over (e.g., "items")
24073
- * @returns The created section, or null if boundaries are invalid
24244
+ * @returns The created section/loop, or null if creation failed
24074
24245
  */
24075
24246
  createRepeatingSection(startIndex, endIndex, fieldPath) {
24076
24247
  if (!this._isReady) {
24077
24248
  throw new Error('Editor is not ready');
24078
24249
  }
24250
+ // If a table is focused, create a row loop instead of a text repeating section
24251
+ const focusedTable = this.getFocusedTable();
24252
+ if (focusedTable && focusedTable.focusedCell) {
24253
+ Logger.log('[pc-editor] createRepeatingSection → table row loop', fieldPath);
24254
+ const row = focusedTable.focusedCell.row;
24255
+ const loop = focusedTable.createRowLoop(row, row, fieldPath);
24256
+ if (loop) {
24257
+ this.canvasManager.render();
24258
+ this.emit('table-row-loop-added', { table: focusedTable, loop });
24259
+ }
24260
+ return null; // Row loops are not RepeatingSections, return null
24261
+ }
24079
24262
  // Repeating sections only work in body (document-level)
24263
+ Logger.log('[pc-editor] createRepeatingSection', startIndex, endIndex, fieldPath);
24080
24264
  const section = this.document.bodyFlowingContent.createRepeatingSection(startIndex, endIndex, fieldPath);
24081
24265
  if (section) {
24082
24266
  this.canvasManager.render();
@@ -24454,7 +24638,7 @@ class PCEditor extends EventEmitter {
24454
24638
  createEmbeddedObjectFromData(data) {
24455
24639
  const object = EmbeddedObjectFactory.tryCreate(data);
24456
24640
  if (!object) {
24457
- console.warn('Unknown object type:', data.objectType);
24641
+ Logger.warn('[pc-editor] Unknown object type:', data.objectType);
24458
24642
  }
24459
24643
  return object;
24460
24644
  }
@@ -24483,6 +24667,13 @@ class PCEditor extends EventEmitter {
24483
24667
  return false;
24484
24668
  }
24485
24669
  }
24670
+ /**
24671
+ * Enable or disable verbose logging.
24672
+ * When disabled (default), only errors are logged to the console.
24673
+ */
24674
+ setLogging(enabled) {
24675
+ Logger.setEnabled(enabled);
24676
+ }
24486
24677
  destroy() {
24487
24678
  this.disableTextInput();
24488
24679
  if (this.canvasManager) {
@@ -25625,33 +25816,38 @@ class DocumentInfoPane extends BasePane {
25625
25816
  }
25626
25817
  createContent() {
25627
25818
  const container = document.createElement('div');
25628
- container.className = 'pc-pane-info-list';
25819
+ container.className = 'pc-pane-label-value-grid';
25629
25820
  // Page count
25630
- const countRow = this.createInfoRow('Pages', '0');
25631
- this.pageCountEl = countRow.querySelector('.pc-pane-info-value');
25632
- container.appendChild(countRow);
25821
+ container.appendChild(this.createLabel('Pages:'));
25822
+ this.pageCountEl = this.createValue('0');
25823
+ container.appendChild(this.pageCountEl);
25824
+ container.appendChild(this.createSpacer());
25633
25825
  // Page size
25634
- const sizeRow = this.createInfoRow('Size', '-');
25635
- this.pageSizeEl = sizeRow.querySelector('.pc-pane-info-value');
25636
- container.appendChild(sizeRow);
25826
+ container.appendChild(this.createLabel('Size:'));
25827
+ this.pageSizeEl = this.createValue('-');
25828
+ container.appendChild(this.pageSizeEl);
25829
+ container.appendChild(this.createSpacer());
25637
25830
  // Page orientation
25638
- const orientationRow = this.createInfoRow('Orientation', '-');
25639
- this.pageOrientationEl = orientationRow.querySelector('.pc-pane-info-value');
25640
- container.appendChild(orientationRow);
25831
+ container.appendChild(this.createLabel('Orientation:'));
25832
+ this.pageOrientationEl = this.createValue('-');
25833
+ container.appendChild(this.pageOrientationEl);
25834
+ container.appendChild(this.createSpacer());
25641
25835
  return container;
25642
25836
  }
25643
- createInfoRow(label, value) {
25644
- const row = document.createElement('div');
25645
- row.className = 'pc-pane-info';
25646
- const labelEl = document.createElement('span');
25647
- labelEl.className = 'pc-pane-info-label';
25648
- labelEl.textContent = label;
25649
- const valueEl = document.createElement('span');
25650
- valueEl.className = 'pc-pane-info-value';
25651
- valueEl.textContent = value;
25652
- row.appendChild(labelEl);
25653
- row.appendChild(valueEl);
25654
- return row;
25837
+ createLabel(text) {
25838
+ const label = document.createElement('span');
25839
+ label.className = 'pc-pane-label pc-pane-margin-label';
25840
+ label.textContent = text;
25841
+ return label;
25842
+ }
25843
+ createValue(text) {
25844
+ const value = document.createElement('span');
25845
+ value.className = 'pc-pane-info-value';
25846
+ value.textContent = text;
25847
+ return value;
25848
+ }
25849
+ createSpacer() {
25850
+ return document.createElement('div');
25655
25851
  }
25656
25852
  /**
25657
25853
  * Update the displayed information from the editor.
@@ -25831,24 +26027,48 @@ class DocumentSettingsPane extends BasePane {
25831
26027
  const container = document.createElement('div');
25832
26028
  // Margins section
25833
26029
  const marginsSection = this.createSection('Margins (mm)');
26030
+ // Five-column grid: label, edit, label, edit, stretch
25834
26031
  const marginsGrid = document.createElement('div');
25835
- marginsGrid.className = 'pc-pane-margins-grid';
26032
+ marginsGrid.className = 'pc-pane-margins-grid-5col';
25836
26033
  this.marginTopInput = this.createNumberInput({ min: 5, max: 50, step: 0.5, value: 20 });
25837
26034
  this.marginRightInput = this.createNumberInput({ min: 5, max: 50, step: 0.5, value: 20 });
25838
26035
  this.marginBottomInput = this.createNumberInput({ min: 5, max: 50, step: 0.5, value: 20 });
25839
26036
  this.marginLeftInput = this.createNumberInput({ min: 5, max: 50, step: 0.5, value: 20 });
25840
- marginsGrid.appendChild(this.createFormGroup('Top', this.marginTopInput, { inline: true }));
25841
- marginsGrid.appendChild(this.createFormGroup('Right', this.marginRightInput, { inline: true }));
25842
- marginsGrid.appendChild(this.createFormGroup('Bottom', this.marginBottomInput, { inline: true }));
25843
- marginsGrid.appendChild(this.createFormGroup('Left', this.marginLeftInput, { inline: true }));
26037
+ // Apply margins on blur
26038
+ const applyMargins = () => this.applyMargins();
26039
+ this.marginTopInput.addEventListener('blur', applyMargins);
26040
+ this.marginRightInput.addEventListener('blur', applyMargins);
26041
+ this.marginBottomInput.addEventListener('blur', applyMargins);
26042
+ this.marginLeftInput.addEventListener('blur', applyMargins);
26043
+ this.eventCleanup.push(() => {
26044
+ this.marginTopInput?.removeEventListener('blur', applyMargins);
26045
+ this.marginRightInput?.removeEventListener('blur', applyMargins);
26046
+ this.marginBottomInput?.removeEventListener('blur', applyMargins);
26047
+ this.marginLeftInput?.removeEventListener('blur', applyMargins);
26048
+ });
26049
+ // Row 1: Top / Right
26050
+ const topLabel = this.createMarginLabel('Top:');
26051
+ const rightLabel = this.createMarginLabel('Right:');
26052
+ marginsGrid.appendChild(topLabel);
26053
+ marginsGrid.appendChild(this.marginTopInput);
26054
+ marginsGrid.appendChild(rightLabel);
26055
+ marginsGrid.appendChild(this.marginRightInput);
26056
+ marginsGrid.appendChild(this.createSpacer());
26057
+ // Row 2: Bottom / Left
26058
+ const bottomLabel = this.createMarginLabel('Bottom:');
26059
+ const leftLabel = this.createMarginLabel('Left:');
26060
+ marginsGrid.appendChild(bottomLabel);
26061
+ marginsGrid.appendChild(this.marginBottomInput);
26062
+ marginsGrid.appendChild(leftLabel);
26063
+ marginsGrid.appendChild(this.marginLeftInput);
26064
+ marginsGrid.appendChild(this.createSpacer());
25844
26065
  marginsSection.appendChild(marginsGrid);
25845
- // Apply margins button
25846
- const applyMarginsBtn = this.createButton('Apply Margins');
25847
- this.addButtonListener(applyMarginsBtn, () => this.applyMargins());
25848
- marginsSection.appendChild(applyMarginsBtn);
25849
26066
  container.appendChild(marginsSection);
25850
- // Page size section
25851
- const pageSizeSection = this.createSection();
26067
+ // Page settings section using label-value grid: label, value, stretch
26068
+ const pageSection = this.createSection();
26069
+ const pageGrid = document.createElement('div');
26070
+ pageGrid.className = 'pc-pane-label-value-grid';
26071
+ // Page Size
25852
26072
  this.pageSizeSelect = this.createSelect([
25853
26073
  { value: 'A4', label: 'A4' },
25854
26074
  { value: 'Letter', label: 'Letter' },
@@ -25856,19 +26076,32 @@ class DocumentSettingsPane extends BasePane {
25856
26076
  { value: 'A3', label: 'A3' }
25857
26077
  ], 'A4');
25858
26078
  this.addImmediateApplyListener(this.pageSizeSelect, () => this.applyPageSettings());
25859
- pageSizeSection.appendChild(this.createFormGroup('Page Size', this.pageSizeSelect));
25860
- container.appendChild(pageSizeSection);
25861
- // Orientation section
25862
- const orientationSection = this.createSection();
26079
+ pageGrid.appendChild(this.createMarginLabel('Page Size:'));
26080
+ pageGrid.appendChild(this.pageSizeSelect);
26081
+ pageGrid.appendChild(this.createSpacer());
26082
+ // Orientation
25863
26083
  this.orientationSelect = this.createSelect([
25864
26084
  { value: 'portrait', label: 'Portrait' },
25865
26085
  { value: 'landscape', label: 'Landscape' }
25866
26086
  ], 'portrait');
25867
26087
  this.addImmediateApplyListener(this.orientationSelect, () => this.applyPageSettings());
25868
- orientationSection.appendChild(this.createFormGroup('Orientation', this.orientationSelect));
25869
- container.appendChild(orientationSection);
26088
+ pageGrid.appendChild(this.createMarginLabel('Orientation:'));
26089
+ pageGrid.appendChild(this.orientationSelect);
26090
+ pageGrid.appendChild(this.createSpacer());
26091
+ pageSection.appendChild(pageGrid);
26092
+ container.appendChild(pageSection);
25870
26093
  return container;
25871
26094
  }
26095
+ createMarginLabel(text) {
26096
+ const label = document.createElement('label');
26097
+ label.className = 'pc-pane-label pc-pane-margin-label';
26098
+ label.textContent = text;
26099
+ return label;
26100
+ }
26101
+ createSpacer() {
26102
+ const spacer = document.createElement('div');
26103
+ return spacer;
26104
+ }
25872
26105
  loadSettings() {
25873
26106
  if (!this.editor)
25874
26107
  return;
@@ -26408,6 +26641,7 @@ class HyperlinkPane extends BasePane {
26408
26641
  this.titleInput = null;
26409
26642
  this.rangeHint = null;
26410
26643
  this.currentHyperlink = null;
26644
+ this._isUpdating = false;
26411
26645
  this.onApply = options.onApply;
26412
26646
  this.onRemove = options.onRemove;
26413
26647
  }
@@ -26449,15 +26683,21 @@ class HyperlinkPane extends BasePane {
26449
26683
  return container;
26450
26684
  }
26451
26685
  updateFromCursor() {
26452
- if (!this.editor)
26686
+ if (!this.editor || this._isUpdating)
26453
26687
  return;
26454
- const cursorPos = this.editor.getCursorPosition();
26455
- const hyperlink = this.editor.getHyperlinkAt(cursorPos);
26456
- if (hyperlink) {
26457
- this.showHyperlink(hyperlink);
26688
+ this._isUpdating = true;
26689
+ try {
26690
+ const cursorPos = this.editor.getCursorPosition();
26691
+ const hyperlink = this.editor.getHyperlinkAt(cursorPos);
26692
+ if (hyperlink) {
26693
+ this.showHyperlink(hyperlink);
26694
+ }
26695
+ else {
26696
+ this.hideHyperlink();
26697
+ }
26458
26698
  }
26459
- else {
26460
- this.hideHyperlink();
26699
+ finally {
26700
+ this._isUpdating = false;
26461
26701
  }
26462
26702
  }
26463
26703
  showHyperlink(hyperlink) {
@@ -27076,6 +27316,7 @@ class TextBoxPane extends BasePane {
27076
27316
  this.borderColorInput = null;
27077
27317
  this.borderStyleSelect = null;
27078
27318
  this.paddingInput = null;
27319
+ this._isUpdating = false;
27079
27320
  this.currentTextBox = null;
27080
27321
  this.onApplyCallback = options.onApply;
27081
27322
  }
@@ -27149,14 +27390,20 @@ class TextBoxPane extends BasePane {
27149
27390
  return container;
27150
27391
  }
27151
27392
  updateFromSelection() {
27152
- if (!this.editor)
27393
+ if (!this.editor || this._isUpdating)
27153
27394
  return;
27154
- const textBox = this.editor.getSelectedTextBox?.();
27155
- if (textBox && !textBox.editing) {
27156
- this.showTextBox(textBox);
27395
+ this._isUpdating = true;
27396
+ try {
27397
+ const textBox = this.editor.getSelectedTextBox?.();
27398
+ if (textBox && !textBox.editing) {
27399
+ this.showTextBox(textBox);
27400
+ }
27401
+ else {
27402
+ this.hideTextBox();
27403
+ }
27157
27404
  }
27158
- else {
27159
- this.hideTextBox();
27405
+ finally {
27406
+ this._isUpdating = false;
27160
27407
  }
27161
27408
  }
27162
27409
  /**
@@ -27310,6 +27557,7 @@ class ImagePane extends BasePane {
27310
27557
  this.altTextInput = null;
27311
27558
  this.fileInput = null;
27312
27559
  this.currentImage = null;
27560
+ this._isUpdating = false;
27313
27561
  this.maxImageWidth = options.maxImageWidth ?? 400;
27314
27562
  this.maxImageHeight = options.maxImageHeight ?? 400;
27315
27563
  this.onApplyCallback = options.onApply;
@@ -27391,14 +27639,20 @@ class ImagePane extends BasePane {
27391
27639
  return container;
27392
27640
  }
27393
27641
  updateFromSelection() {
27394
- if (!this.editor)
27642
+ if (!this.editor || this._isUpdating)
27395
27643
  return;
27396
- const image = this.editor.getSelectedImage?.();
27397
- if (image) {
27398
- this.showImage(image);
27644
+ this._isUpdating = true;
27645
+ try {
27646
+ const image = this.editor.getSelectedImage?.();
27647
+ if (image) {
27648
+ this.showImage(image);
27649
+ }
27650
+ else {
27651
+ this.hideImage();
27652
+ }
27399
27653
  }
27400
- else {
27401
- this.hideImage();
27654
+ finally {
27655
+ this._isUpdating = false;
27402
27656
  }
27403
27657
  }
27404
27658
  /**
@@ -27563,6 +27817,8 @@ class TablePane extends BasePane {
27563
27817
  // Default controls
27564
27818
  this.defaultPaddingInput = null;
27565
27819
  this.defaultBorderColorInput = null;
27820
+ // Row loop controls
27821
+ this.loopFieldInput = null;
27566
27822
  // Cell formatting controls
27567
27823
  this.cellBgColorInput = null;
27568
27824
  this.borderTopCheck = null;
@@ -27573,6 +27829,7 @@ class TablePane extends BasePane {
27573
27829
  this.borderColorInput = null;
27574
27830
  this.borderStyleSelect = null;
27575
27831
  this.currentTable = null;
27832
+ this._isUpdating = false;
27576
27833
  this.onApplyCallback = options.onApply;
27577
27834
  }
27578
27835
  attach(options) {
@@ -27639,6 +27896,16 @@ class TablePane extends BasePane {
27639
27896
  this.addButtonListener(applyHeadersBtn, () => this.applyHeaders());
27640
27897
  headersSection.appendChild(applyHeadersBtn);
27641
27898
  container.appendChild(headersSection);
27899
+ // Row Loop section
27900
+ const loopSection = this.createSection('Row Loop');
27901
+ this.loopFieldInput = this.createTextInput({ placeholder: 'items' });
27902
+ loopSection.appendChild(this.createFormGroup('Array Field', this.loopFieldInput, {
27903
+ hint: 'Creates a loop on the currently focused row'
27904
+ }));
27905
+ const createLoopBtn = this.createButton('Create Row Loop');
27906
+ this.addButtonListener(createLoopBtn, () => this.createRowLoop());
27907
+ loopSection.appendChild(createLoopBtn);
27908
+ container.appendChild(loopSection);
27642
27909
  // Defaults section
27643
27910
  const defaultsSection = this.createSection('Defaults');
27644
27911
  const defaultsRow = this.createRow();
@@ -27711,14 +27978,20 @@ class TablePane extends BasePane {
27711
27978
  return container;
27712
27979
  }
27713
27980
  updateFromFocusedTable() {
27714
- if (!this.editor)
27981
+ if (!this.editor || this._isUpdating)
27715
27982
  return;
27716
- const table = this.editor.getFocusedTable();
27717
- if (table) {
27718
- this.showTable(table);
27983
+ this._isUpdating = true;
27984
+ try {
27985
+ const table = this.editor.getFocusedTable();
27986
+ if (table) {
27987
+ this.showTable(table);
27988
+ }
27989
+ else {
27990
+ this.hideTable();
27991
+ }
27719
27992
  }
27720
- else {
27721
- this.hideTable();
27993
+ finally {
27994
+ this._isUpdating = false;
27722
27995
  }
27723
27996
  }
27724
27997
  /**
@@ -27922,6 +28195,21 @@ class TablePane extends BasePane {
27922
28195
  hasTable() {
27923
28196
  return this.currentTable !== null;
27924
28197
  }
28198
+ createRowLoop() {
28199
+ if (!this.editor || !this.currentTable) {
28200
+ this.onApplyCallback?.(false, new Error('No table focused'));
28201
+ return;
28202
+ }
28203
+ const fieldPath = this.loopFieldInput?.value.trim() || '';
28204
+ if (!fieldPath) {
28205
+ this.onApplyCallback?.(false, new Error('Array field path is required'));
28206
+ return;
28207
+ }
28208
+ // Uses the unified createRepeatingSection API which detects
28209
+ // that a table is focused and creates a row loop on the focused row
28210
+ this.editor.createRepeatingSection(0, 0, fieldPath);
28211
+ this.onApplyCallback?.(true);
28212
+ }
27925
28213
  /**
27926
28214
  * Update the pane from current editor state.
27927
28215
  */
@@ -27930,5 +28218,5 @@ class TablePane extends BasePane {
27930
28218
  }
27931
28219
  }
27932
28220
 
27933
- export { BaseControl, BaseEmbeddedObject, BasePane, BaseTextRegion, BodyTextRegion, ClipboardManager, ContentAnalyzer, DEFAULT_IMPORT_OPTIONS, Document, DocumentBuilder, DocumentInfoPane, DocumentSettingsPane, EmbeddedObjectFactory, EmbeddedObjectManager, EventEmitter, FlowingTextContent, FooterTextRegion, FormattingPane, HeaderTextRegion, HorizontalRuler, HtmlConverter, HyperlinkPane, ImageObject, ImagePane, MergeDataPane, PCEditor, PDFImportError, PDFImportErrorCode, PDFImporter, PDFParser, Page, RegionManager, RepeatingSectionManager, RepeatingSectionPane, RulerControl, SubstitutionFieldManager, SubstitutionFieldPane, TableCell, TableObject, TablePane, TableRow, TableRowLoopPane, TextBoxObject, TextBoxPane, TextFormattingManager, TextLayout, TextMeasurer, TextPositionCalculator, TextState, VerticalRuler, ViewSettingsPane };
28221
+ export { BaseControl, BaseEmbeddedObject, BasePane, BaseTextRegion, BodyTextRegion, ClipboardManager, ContentAnalyzer, DEFAULT_IMPORT_OPTIONS, Document, DocumentBuilder, DocumentInfoPane, DocumentSettingsPane, EmbeddedObjectFactory, EmbeddedObjectManager, EventEmitter, FlowingTextContent, FooterTextRegion, FormattingPane, HeaderTextRegion, HorizontalRuler, HtmlConverter, HyperlinkPane, ImageObject, ImagePane, Logger, MergeDataPane, PCEditor, PDFImportError, PDFImportErrorCode, PDFImporter, PDFParser, Page, RegionManager, RepeatingSectionManager, RepeatingSectionPane, RulerControl, SubstitutionFieldManager, SubstitutionFieldPane, TableCell, TableObject, TablePane, TableRow, TableRowLoopPane, TextBoxObject, TextBoxPane, TextFormattingManager, TextLayout, TextMeasurer, TextPositionCalculator, TextState, VerticalRuler, ViewSettingsPane };
27934
28222
  //# sourceMappingURL=pc-editor.esm.js.map