@productcloudos/editor 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/pc-editor.esm.js +588 -226
  2. package/dist/pc-editor.esm.js.map +1 -1
  3. package/dist/pc-editor.js +588 -225
  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 +31 -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/FormattingPane.d.ts.map +1 -1
  27. package/dist/types/lib/panes/HyperlinkPane.d.ts +1 -0
  28. package/dist/types/lib/panes/HyperlinkPane.d.ts.map +1 -1
  29. package/dist/types/lib/panes/ImagePane.d.ts +1 -0
  30. package/dist/types/lib/panes/ImagePane.d.ts.map +1 -1
  31. package/dist/types/lib/panes/TablePane.d.ts +5 -0
  32. package/dist/types/lib/panes/TablePane.d.ts.map +1 -1
  33. package/dist/types/lib/panes/TextBoxPane.d.ts +1 -0
  34. package/dist/types/lib/panes/TextBoxPane.d.ts.map +1 -1
  35. package/dist/types/lib/rendering/CanvasManager.d.ts +1 -0
  36. package/dist/types/lib/rendering/CanvasManager.d.ts.map +1 -1
  37. package/dist/types/lib/rendering/FlowingTextRenderer.d.ts.map +1 -1
  38. package/dist/types/lib/rendering/PDFGenerator.d.ts.map +1 -1
  39. package/dist/types/lib/text/FlowingTextContent.d.ts.map +1 -1
  40. package/dist/types/lib/text/TextFormatting.d.ts +12 -1
  41. package/dist/types/lib/text/TextFormatting.d.ts.map +1 -1
  42. package/dist/types/lib/types/index.d.ts +2 -0
  43. package/dist/types/lib/types/index.d.ts.map +1 -1
  44. package/dist/types/lib/undo/transaction/MutationUndo.d.ts.map +1 -1
  45. package/dist/types/lib/utils/logger.d.ts +20 -0
  46. package/dist/types/lib/utils/logger.d.ts.map +1 -0
  47. 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
@@ -7617,6 +7758,9 @@ class TableObject extends BaseEmbeddedObject {
7617
7758
  result.mergedCell.markReflowDirty();
7618
7759
  }
7619
7760
  this.clearSelection();
7761
+ // Focus the anchor (top-left) cell of the merged range
7762
+ const normalized = TableCellMerger.normalizeRange(mergeRange);
7763
+ this.focusCell(normalized.start.row, normalized.start.col);
7620
7764
  this.emit('cells-merged', { range: mergeRange });
7621
7765
  this.emit('content-changed', {});
7622
7766
  }
@@ -8296,14 +8440,20 @@ class EmbeddedObjectFactory {
8296
8440
  border: data.data.border,
8297
8441
  padding: data.data.padding
8298
8442
  });
8299
- // Restore formatting runs if present
8443
+ // Restore formatting runs (run-based: each entry applies from its index to the next)
8300
8444
  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);
8445
+ const runs = data.data.formattingRuns;
8446
+ if (runs.length > 0) {
8447
+ const formattingManager = textBox.flowingContent.getFormattingManager();
8448
+ const textLength = textBox.flowingContent.getText().length;
8449
+ for (let i = 0; i < runs.length; i++) {
8450
+ const [startIndex, style] = runs[i];
8451
+ const nextIndex = i + 1 < runs.length ? runs[i + 1][0] : textLength;
8452
+ if (startIndex < nextIndex) {
8453
+ formattingManager.applyFormatting(startIndex, nextIndex, style);
8454
+ }
8455
+ }
8305
8456
  }
8306
- formattingManager.setAllFormatting(formattingMap);
8307
8457
  }
8308
8458
  // Restore substitution fields if present
8309
8459
  if (data.data.substitutionFields && Array.isArray(data.data.substitutionFields)) {
@@ -9280,9 +9430,9 @@ class FlowingTextContent extends EventEmitter {
9280
9430
  * @returns true if the event was handled, false otherwise
9281
9431
  */
9282
9432
  handleKeyDown(e) {
9283
- console.log('[FlowingTextContent.handleKeyDown] Key:', e.key, '_hasFocus:', this._hasFocus);
9433
+ Logger.log('[pc-editor:FlowingTextContent.handleKeyDown] Key:', e.key, '_hasFocus:', this._hasFocus);
9284
9434
  if (!this._hasFocus) {
9285
- console.log('[FlowingTextContent.handleKeyDown] No focus, returning false');
9435
+ Logger.log('[pc-editor:FlowingTextContent.handleKeyDown] No focus, returning false');
9286
9436
  return false;
9287
9437
  }
9288
9438
  switch (e.key) {
@@ -9797,46 +9947,19 @@ class FlowingTextContent extends EventEmitter {
9797
9947
  toData() {
9798
9948
  // Serialize text content
9799
9949
  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;
9950
+ // Serialize text formatting as compressed runs (only at change boundaries)
9951
+ const compressedRuns = this.formatting.getCompressedRuns(text.length);
9952
+ const formattingRuns = compressedRuns.map(run => ({
9953
+ index: run.index,
9954
+ formatting: {
9955
+ fontFamily: run.formatting.fontFamily,
9956
+ fontSize: run.formatting.fontSize,
9957
+ fontWeight: run.formatting.fontWeight,
9958
+ fontStyle: run.formatting.fontStyle,
9959
+ color: run.formatting.color,
9960
+ backgroundColor: run.formatting.backgroundColor
9838
9961
  }
9839
- }
9962
+ }));
9840
9963
  // Serialize paragraph formatting
9841
9964
  const paragraphFormatting = this.paragraphFormatting.toJSON();
9842
9965
  // Serialize substitution fields
@@ -9932,7 +10055,7 @@ class FlowingTextContent extends EventEmitter {
9932
10055
  content.getEmbeddedObjectManager().insert(object, ref.textIndex);
9933
10056
  }
9934
10057
  else {
9935
- console.warn(`Failed to create embedded object of type: ${ref.object.objectType}`);
10058
+ Logger.warn(`[pc-editor:FlowingTextContent] Failed to create embedded object of type: ${ref.object.objectType}`);
9936
10059
  }
9937
10060
  }
9938
10061
  }
@@ -9986,7 +10109,7 @@ class FlowingTextContent extends EventEmitter {
9986
10109
  this.embeddedObjects.insert(object, ref.textIndex);
9987
10110
  }
9988
10111
  else {
9989
- console.warn(`Failed to create embedded object of type: ${ref.object.objectType}`);
10112
+ Logger.warn(`[pc-editor:FlowingTextContent] Failed to create embedded object of type: ${ref.object.objectType}`);
9990
10113
  }
9991
10114
  }
9992
10115
  }
@@ -12525,7 +12648,7 @@ class FlowingTextRenderer extends EventEmitter {
12525
12648
  updateResizeHandleTargets(selectedObjects) {
12526
12649
  // Clear existing resize handle targets
12527
12650
  this._hitTestManager.clearCategory('resize-handles');
12528
- console.log('[updateResizeHandleTargets] selectedObjects:', selectedObjects.length);
12651
+ Logger.log('[pc-editor:FlowingTextRenderer] updateResizeHandleTargets selectedObjects:', selectedObjects.length);
12529
12652
  // Register resize handles for each selected object
12530
12653
  for (const object of selectedObjects) {
12531
12654
  if (!object.resizable)
@@ -12538,7 +12661,7 @@ class FlowingTextRenderer extends EventEmitter {
12538
12661
  // For regular objects, use renderedPosition
12539
12662
  const pos = object.renderedPosition;
12540
12663
  const pageIndex = object.renderedPageIndex;
12541
- console.log('[updateResizeHandleTargets] object:', object.id, 'pageIndex:', pageIndex, 'pos:', pos);
12664
+ Logger.log('[pc-editor:FlowingTextRenderer] updateResizeHandleTargets object:', object.id, 'pageIndex:', pageIndex, 'pos:', pos);
12542
12665
  if (pos && pageIndex >= 0) {
12543
12666
  this.registerObjectResizeHandles(object, pageIndex, pos);
12544
12667
  }
@@ -14194,7 +14317,8 @@ class CanvasManager extends EventEmitter {
14194
14317
  const pageIndex = this.document.pages.findIndex(p => p.id === pageId);
14195
14318
  // Get the slice for this page (for multi-page tables)
14196
14319
  const slice = table.getRenderedSlice(pageIndex);
14197
- const tablePosition = slice?.position || table.renderedPosition;
14320
+ const tablePosition = slice?.position ||
14321
+ (table.renderedPageIndex === pageIndex ? table.renderedPosition : null);
14198
14322
  const sliceHeight = slice?.height || table.height;
14199
14323
  // Check if point is within the table slice on this page
14200
14324
  const isInsideTable = tablePosition &&
@@ -14227,6 +14351,7 @@ class CanvasManager extends EventEmitter {
14227
14351
  end: cellAddr
14228
14352
  });
14229
14353
  this.render();
14354
+ this.emit('table-cell-selection-changed', { table });
14230
14355
  e.preventDefault();
14231
14356
  return;
14232
14357
  }
@@ -14506,7 +14631,8 @@ class CanvasManager extends EventEmitter {
14506
14631
  const currentPageIndex = this.document.pages.findIndex(p => p.id === pageId);
14507
14632
  // Get the slice for the current page (for multi-page tables)
14508
14633
  const slice = table.getRenderedSlice(currentPageIndex);
14509
- const tablePosition = slice?.position || table.renderedPosition;
14634
+ const tablePosition = slice?.position ||
14635
+ (table.renderedPageIndex === currentPageIndex ? table.renderedPosition : null);
14510
14636
  const sliceHeight = slice?.height || table.height;
14511
14637
  if (tablePosition) {
14512
14638
  // Check if point is within the table slice on this page
@@ -14536,6 +14662,7 @@ class CanvasManager extends EventEmitter {
14536
14662
  end: cellAddr
14537
14663
  });
14538
14664
  this.render();
14665
+ this.emit('table-cell-selection-changed', { table });
14539
14666
  }
14540
14667
  }
14541
14668
  }
@@ -15079,40 +15206,46 @@ class CanvasManager extends EventEmitter {
15079
15206
  canvas.style.cursor = 'move';
15080
15207
  return;
15081
15208
  }
15082
- // Show text cursor for text boxes
15083
- if (object instanceof TextBoxObject) {
15084
- canvas.style.cursor = 'text';
15085
- return;
15209
+ // Show text cursor for objects in edit mode, arrow otherwise
15210
+ if (object instanceof TextBoxObject && this.editingTextBox === object) {
15211
+ canvas.style.cursor = CanvasManager.TEXT_CURSOR;
15086
15212
  }
15213
+ else if (object instanceof TableObject && this._focusedControl === object) {
15214
+ canvas.style.cursor = CanvasManager.TEXT_CURSOR;
15215
+ }
15216
+ else {
15217
+ canvas.style.cursor = 'default';
15218
+ }
15219
+ return;
15087
15220
  }
15088
15221
  }
15089
15222
  // Check for table cells (show text cursor)
15090
15223
  const tableCellHit = hitTestManager.queryByType(pageIndex, point, 'table-cell');
15091
15224
  if (tableCellHit && tableCellHit.data.type === 'table-cell') {
15092
- canvas.style.cursor = 'text';
15225
+ canvas.style.cursor = CanvasManager.TEXT_CURSOR;
15093
15226
  return;
15094
15227
  }
15095
15228
  // Check for text regions (body, header, footer - show text cursor)
15096
15229
  const textRegionHit = hitTestManager.queryByType(pageIndex, point, 'text-region');
15097
15230
  if (textRegionHit && textRegionHit.data.type === 'text-region') {
15098
- canvas.style.cursor = 'text';
15231
+ canvas.style.cursor = CanvasManager.TEXT_CURSOR;
15099
15232
  return;
15100
15233
  }
15101
15234
  // Also check if point is within any editable region (body, header, footer)
15102
15235
  // This catches cases where text region hit targets may not cover empty space
15103
15236
  const bodyRegion = this.regionManager.getBodyRegion();
15104
15237
  if (bodyRegion && bodyRegion.containsPointInRegion(point, pageIndex)) {
15105
- canvas.style.cursor = 'text';
15238
+ canvas.style.cursor = CanvasManager.TEXT_CURSOR;
15106
15239
  return;
15107
15240
  }
15108
15241
  const headerRegion = this.regionManager.getHeaderRegion();
15109
15242
  if (headerRegion && headerRegion.containsPointInRegion(point, pageIndex)) {
15110
- canvas.style.cursor = 'text';
15243
+ canvas.style.cursor = CanvasManager.TEXT_CURSOR;
15111
15244
  return;
15112
15245
  }
15113
15246
  const footerRegion = this.regionManager.getFooterRegion();
15114
15247
  if (footerRegion && footerRegion.containsPointInRegion(point, pageIndex)) {
15115
- canvas.style.cursor = 'text';
15248
+ canvas.style.cursor = CanvasManager.TEXT_CURSOR;
15116
15249
  return;
15117
15250
  }
15118
15251
  canvas.style.cursor = 'default';
@@ -15130,7 +15263,8 @@ class CanvasManager extends EventEmitter {
15130
15263
  const { table, dividerType, index } = target.data;
15131
15264
  // Get the table position from slice info
15132
15265
  const slice = table.getRenderedSlice(pageIndex);
15133
- const tablePosition = slice?.position || table.renderedPosition;
15266
+ const tablePosition = slice?.position ||
15267
+ (table.renderedPageIndex === pageIndex ? table.renderedPosition : null);
15134
15268
  if (tablePosition) {
15135
15269
  // Calculate the divider position based on type and index
15136
15270
  let position;
@@ -15184,7 +15318,7 @@ class CanvasManager extends EventEmitter {
15184
15318
  this.emit('element-removed', { elementId: objectId });
15185
15319
  }
15186
15320
  selectElement(elementId) {
15187
- console.log('Selecting element:', elementId);
15321
+ Logger.log('[pc-editor:CanvasManager] Selecting element:', elementId);
15188
15322
  this.selectedElements.add(elementId);
15189
15323
  // Update embedded object's selected state
15190
15324
  const flowingContents = [
@@ -15196,12 +15330,12 @@ class CanvasManager extends EventEmitter {
15196
15330
  const embeddedObjects = flowingContent.getEmbeddedObjects();
15197
15331
  for (const [, obj] of embeddedObjects.entries()) {
15198
15332
  if (obj.id === elementId) {
15199
- console.log('Found embedded object to select:', obj.id);
15333
+ Logger.log('[pc-editor:CanvasManager] Found embedded object to select:', obj.id);
15200
15334
  obj.selected = true;
15201
15335
  }
15202
15336
  }
15203
15337
  }
15204
- console.log('Selected elements after selection:', Array.from(this.selectedElements));
15338
+ Logger.log('[pc-editor:CanvasManager] Selected elements after selection:', Array.from(this.selectedElements));
15205
15339
  this.render();
15206
15340
  this.updateResizeHandleHitTargets();
15207
15341
  this.emit('selection-change', { selectedElements: Array.from(this.selectedElements) });
@@ -15251,10 +15385,10 @@ class CanvasManager extends EventEmitter {
15251
15385
  this.flowingTextRenderer.updateResizeHandleTargets(selectedObjects);
15252
15386
  }
15253
15387
  clearSelection() {
15254
- console.log('clearSelection called, current selected elements:', Array.from(this.selectedElements));
15388
+ Logger.log('[pc-editor:CanvasManager] clearSelection called, current selected elements:', Array.from(this.selectedElements));
15255
15389
  // Clear selected state on all embedded objects
15256
15390
  this.selectedElements.forEach(elementId => {
15257
- console.log('Clearing selection for element:', elementId);
15391
+ Logger.log('[pc-editor:CanvasManager] Clearing selection for element:', elementId);
15258
15392
  // Check embedded objects in all flowing content sources (body, header, footer)
15259
15393
  const flowingContents = [
15260
15394
  this.document.bodyFlowingContent,
@@ -15265,7 +15399,7 @@ class CanvasManager extends EventEmitter {
15265
15399
  const embeddedObjects = flowingContent.getEmbeddedObjects();
15266
15400
  for (const [, embeddedObj] of embeddedObjects.entries()) {
15267
15401
  if (embeddedObj.id === elementId) {
15268
- console.log('Clearing selection on embedded object:', elementId);
15402
+ Logger.log('[pc-editor:CanvasManager] Clearing selection on embedded object:', elementId);
15269
15403
  embeddedObj.selected = false;
15270
15404
  }
15271
15405
  }
@@ -15273,7 +15407,7 @@ class CanvasManager extends EventEmitter {
15273
15407
  });
15274
15408
  this.selectedElements.clear();
15275
15409
  this.selectedSectionId = null;
15276
- console.log('About to render after clearing selection...');
15410
+ Logger.log('[pc-editor:CanvasManager] About to render after clearing selection...');
15277
15411
  this.render();
15278
15412
  this.updateResizeHandleHitTargets();
15279
15413
  this.emit('selection-change', { selectedElements: [] });
@@ -15522,7 +15656,7 @@ class CanvasManager extends EventEmitter {
15522
15656
  });
15523
15657
  // Handle substitution field clicks
15524
15658
  this.flowingTextRenderer.on('substitution-field-clicked', (data) => {
15525
- console.log('[substitution-field-clicked] Field:', data.field?.fieldName, 'Section:', data.section);
15659
+ Logger.log('[pc-editor:CanvasManager] substitution-field-clicked Field:', data.field?.fieldName, 'Section:', data.section);
15526
15660
  // Emit event for external handling (e.g., showing field properties panel)
15527
15661
  this.emit('substitution-field-clicked', data);
15528
15662
  });
@@ -15858,7 +15992,9 @@ class CanvasManager extends EventEmitter {
15858
15992
  if (obj instanceof TableObject) {
15859
15993
  // For multi-page tables, check if this page has a rendered slice
15860
15994
  const slice = obj.getRenderedSlice(pageIndex);
15861
- const tablePosition = slice?.position || obj.renderedPosition;
15995
+ // Only use renderedPosition if the table was actually rendered on this page
15996
+ const tablePosition = slice?.position ||
15997
+ (obj.renderedPageIndex === pageIndex ? obj.renderedPosition : null);
15862
15998
  if (tablePosition) {
15863
15999
  // Check if point is inside the table slice on this page
15864
16000
  const sliceHeight = slice?.height || obj.height;
@@ -15997,14 +16133,15 @@ class CanvasManager extends EventEmitter {
15997
16133
  this.editingTextBox = textBox;
15998
16134
  this._editingTextBoxPageId = pageId || null;
15999
16135
  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
16136
  // Clear selection in main flowing content
16004
16137
  this.document.bodyFlowingContent.clearSelection();
16005
- // Select the text box
16138
+ // Select the text box visually (this calls setFocus(null) internally,
16139
+ // so we must set focus to the text box AFTER this call)
16006
16140
  this.clearSelection();
16007
16141
  this.selectInlineElement({ type: 'embedded-object', object: textBox, textIndex: textBox.textIndex });
16142
+ // Now set focus to the text box for editing — must be AFTER selectInlineElement
16143
+ // because selectBaseEmbeddedObject calls setFocus(null) which would undo it
16144
+ this.setFocus(textBox);
16008
16145
  this.emit('textbox-editing-started', { textBox });
16009
16146
  }
16010
16147
  else {
@@ -16101,6 +16238,10 @@ class CanvasManager extends EventEmitter {
16101
16238
  }
16102
16239
  CanvasManager.CELL_SELECTION_THRESHOLD = 5; // Minimum pixels to drag before cell selection starts
16103
16240
  CanvasManager.RELATIVE_DRAG_THRESHOLD = 3; // Minimum pixels to drag before moving starts
16241
+ // Custom text cursor as a black I-beam SVG data URI.
16242
+ // The native 'text' cursor can render as white on Windows browsers,
16243
+ // making it invisible over the white canvas background.
16244
+ CanvasManager.TEXT_CURSOR = "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='20' viewBox='0 0 16 20'%3E%3Cpath d='M5 1h2v1h2V1h2v2h-2v6h2v2h-2v6h2v2h-2v-1H7v1H5v-2h2v-6H5V9h2V3H5z' fill='%23000'/%3E%3C/svg%3E\") 8 10, text";
16104
16245
 
16105
16246
  /**
16106
16247
  * DataBinder handles binding data to documents.
@@ -16743,7 +16884,15 @@ class PDFGenerator {
16743
16884
  // Check if it's a data URL we can embed
16744
16885
  if (src.startsWith('data:')) {
16745
16886
  try {
16746
- const embeddedImage = await this.embedImageFromDataUrl(pdfDoc, src);
16887
+ let embeddedImage = await this.embedImageFromDataUrl(pdfDoc, src);
16888
+ // If the format isn't directly supported (e.g., SVG, WebP, GIF),
16889
+ // convert to PNG via canvas and try again
16890
+ if (!embeddedImage) {
16891
+ const pngDataUrl = image.toPngDataUrl();
16892
+ if (pngDataUrl) {
16893
+ embeddedImage = await this.embedImageFromDataUrl(pdfDoc, pngDataUrl);
16894
+ }
16895
+ }
16747
16896
  if (embeddedImage) {
16748
16897
  // Calculate draw position/size based on fit mode
16749
16898
  const drawParams = this.calculateImageDrawParams(embeddedImage.width, embeddedImage.height, image.width, image.height, image.fit);
@@ -16766,7 +16915,7 @@ class PDFGenerator {
16766
16915
  }
16767
16916
  }
16768
16917
  catch (e) {
16769
- console.warn('Failed to embed image:', e);
16918
+ Logger.warn('[pc-editor:PDFGenerator] Failed to embed image:', e);
16770
16919
  }
16771
16920
  }
16772
16921
  // Fallback: draw placeholder rectangle for images we can't embed
@@ -18794,7 +18943,7 @@ class MutationUndo {
18794
18943
  this.undoTableStructure(mutation);
18795
18944
  break;
18796
18945
  default:
18797
- console.warn('Unknown mutation type for undo:', mutation.type);
18946
+ Logger.warn('[pc-editor:MutationUndo] Unknown mutation type for undo:', mutation.type);
18798
18947
  }
18799
18948
  }
18800
18949
  /**
@@ -18844,7 +18993,7 @@ class MutationUndo {
18844
18993
  this.redoTableStructure(mutation);
18845
18994
  break;
18846
18995
  default:
18847
- console.warn('Unknown mutation type for redo:', mutation.type);
18996
+ Logger.warn('[pc-editor:MutationUndo] Unknown mutation type for redo:', mutation.type);
18848
18997
  }
18849
18998
  }
18850
18999
  // --- Text Mutations ---
@@ -20176,13 +20325,13 @@ class PDFParser {
20176
20325
  }
20177
20326
  catch {
20178
20327
  // Skip images that fail to extract
20179
- console.warn(`Failed to extract image: ${imageName}`);
20328
+ Logger.warn(`[pc-editor:PDFParser] Failed to extract image: ${imageName}`);
20180
20329
  }
20181
20330
  }
20182
20331
  }
20183
20332
  }
20184
20333
  catch (error) {
20185
- console.warn('Image extraction failed:', error);
20334
+ Logger.warn('[pc-editor:PDFParser] Image extraction failed:', error);
20186
20335
  }
20187
20336
  return images;
20188
20337
  }
@@ -21209,6 +21358,8 @@ class PCEditor extends EventEmitter {
21209
21358
  }
21210
21359
  this.container = container;
21211
21360
  this.options = this.mergeOptions(options);
21361
+ // Initialize logging
21362
+ Logger.setEnabled(this.options.enableLogging ?? false);
21212
21363
  this.document = new Document();
21213
21364
  // Apply constructor options to document settings
21214
21365
  this.document.updateSettings({
@@ -21233,7 +21384,8 @@ class PCEditor extends EventEmitter {
21233
21384
  showControlCharacters: options?.showControlCharacters ?? false,
21234
21385
  defaultFont: options?.defaultFont || 'Arial',
21235
21386
  defaultFontSize: options?.defaultFontSize || 12,
21236
- theme: options?.theme || 'light'
21387
+ theme: options?.theme || 'light',
21388
+ enableLogging: options?.enableLogging ?? false
21237
21389
  };
21238
21390
  }
21239
21391
  initialize() {
@@ -21378,6 +21530,10 @@ class PCEditor extends EventEmitter {
21378
21530
  this.canvasManager.on('tablecell-cursor-changed', (data) => {
21379
21531
  this.emit('tablecell-cursor-changed', data);
21380
21532
  });
21533
+ // Forward table cell selection changes (multi-cell drag/shift-click)
21534
+ this.canvasManager.on('table-cell-selection-changed', (data) => {
21535
+ this.emit('table-cell-selection-changed', data);
21536
+ });
21381
21537
  this.canvasManager.on('repeating-section-clicked', (data) => {
21382
21538
  // Repeating section clicked - update selection state
21383
21539
  if (data.section && data.section.id) {
@@ -21640,6 +21796,7 @@ class PCEditor extends EventEmitter {
21640
21796
  * This changes which section receives keyboard input and cursor positioning.
21641
21797
  */
21642
21798
  setActiveSection(section) {
21799
+ Logger.log('[pc-editor] setActiveSection', section);
21643
21800
  if (this._activeEditingSection !== section) {
21644
21801
  this._activeEditingSection = section;
21645
21802
  // Delegate to canvas manager which handles the section change and emits events
@@ -21730,6 +21887,7 @@ class PCEditor extends EventEmitter {
21730
21887
  }
21731
21888
  }
21732
21889
  loadDocument(documentData) {
21890
+ Logger.log('[pc-editor] loadDocument');
21733
21891
  if (!this._isReady) {
21734
21892
  throw new Error('Editor is not ready');
21735
21893
  }
@@ -21756,6 +21914,7 @@ class PCEditor extends EventEmitter {
21756
21914
  return this.document.toData();
21757
21915
  }
21758
21916
  bindData(data) {
21917
+ Logger.log('[pc-editor] bindData');
21759
21918
  if (!this._isReady) {
21760
21919
  throw new Error('Editor is not ready');
21761
21920
  }
@@ -21764,6 +21923,7 @@ class PCEditor extends EventEmitter {
21764
21923
  this.emit('data-bound', { data });
21765
21924
  }
21766
21925
  async exportPDF(options) {
21926
+ Logger.log('[pc-editor] exportPDF');
21767
21927
  if (!this._isReady) {
21768
21928
  throw new Error('Editor is not ready');
21769
21929
  }
@@ -21805,6 +21965,7 @@ class PCEditor extends EventEmitter {
21805
21965
  * @returns JSON string representation of the document
21806
21966
  */
21807
21967
  saveDocument() {
21968
+ Logger.log('[pc-editor] saveDocument');
21808
21969
  if (!this._isReady) {
21809
21970
  throw new Error('Editor is not ready');
21810
21971
  }
@@ -21816,6 +21977,7 @@ class PCEditor extends EventEmitter {
21816
21977
  * @param filename Optional filename (defaults to 'document.pceditor.json')
21817
21978
  */
21818
21979
  saveDocumentToFile(filename = 'document.pceditor.json') {
21980
+ Logger.log('[pc-editor] saveDocumentToFile', filename);
21819
21981
  const jsonString = this.saveDocument();
21820
21982
  const blob = new Blob([jsonString], { type: 'application/json' });
21821
21983
  const url = URL.createObjectURL(blob);
@@ -21833,6 +21995,7 @@ class PCEditor extends EventEmitter {
21833
21995
  * @param jsonString JSON string representation of the document
21834
21996
  */
21835
21997
  loadDocumentFromJSON(jsonString) {
21998
+ Logger.log('[pc-editor] loadDocumentFromJSON');
21836
21999
  if (!this._isReady) {
21837
22000
  throw new Error('Editor is not ready');
21838
22001
  }
@@ -21853,6 +22016,7 @@ class PCEditor extends EventEmitter {
21853
22016
  * @returns Promise that resolves when loading is complete
21854
22017
  */
21855
22018
  async loadDocumentFromFile(file) {
22019
+ Logger.log('[pc-editor] loadDocumentFromFile', file.name);
21856
22020
  if (!this._isReady) {
21857
22021
  throw new Error('Editor is not ready');
21858
22022
  }
@@ -21941,22 +22105,25 @@ class PCEditor extends EventEmitter {
21941
22105
  // Version compatibility check
21942
22106
  const [major] = doc.version.split('.').map(Number);
21943
22107
  if (major > 1) {
21944
- console.warn(`Document version ${doc.version} may not be fully compatible with this editor`);
22108
+ Logger.warn(`[pc-editor] Document version ${doc.version} may not be fully compatible with this editor`);
21945
22109
  }
21946
22110
  }
21947
22111
  selectElement(elementId) {
22112
+ Logger.log('[pc-editor] selectElement', elementId);
21948
22113
  if (!this._isReady) {
21949
22114
  throw new Error('Editor is not ready');
21950
22115
  }
21951
22116
  this.canvasManager.selectElement(elementId);
21952
22117
  }
21953
22118
  clearSelection() {
22119
+ Logger.log('[pc-editor] clearSelection');
21954
22120
  if (!this._isReady) {
21955
22121
  throw new Error('Editor is not ready');
21956
22122
  }
21957
22123
  this.canvasManager.clearSelection();
21958
22124
  }
21959
22125
  removeEmbeddedObject(objectId) {
22126
+ Logger.log('[pc-editor] removeEmbeddedObject', objectId);
21960
22127
  if (!this._isReady) {
21961
22128
  throw new Error('Editor is not ready');
21962
22129
  }
@@ -21966,6 +22133,7 @@ class PCEditor extends EventEmitter {
21966
22133
  * Undo the last operation.
21967
22134
  */
21968
22135
  undo() {
22136
+ Logger.log('[pc-editor] undo');
21969
22137
  if (!this._isReady)
21970
22138
  return;
21971
22139
  const success = this.transactionManager.undo();
@@ -21978,6 +22146,7 @@ class PCEditor extends EventEmitter {
21978
22146
  * Redo the last undone operation.
21979
22147
  */
21980
22148
  redo() {
22149
+ Logger.log('[pc-editor] redo');
21981
22150
  if (!this._isReady)
21982
22151
  return;
21983
22152
  const success = this.transactionManager.redo();
@@ -22011,16 +22180,19 @@ class PCEditor extends EventEmitter {
22011
22180
  this.transactionManager.setMaxHistory(count);
22012
22181
  }
22013
22182
  zoomIn() {
22183
+ Logger.log('[pc-editor] zoomIn');
22014
22184
  if (!this._isReady)
22015
22185
  return;
22016
22186
  this.canvasManager.zoomIn();
22017
22187
  }
22018
22188
  zoomOut() {
22189
+ Logger.log('[pc-editor] zoomOut');
22019
22190
  if (!this._isReady)
22020
22191
  return;
22021
22192
  this.canvasManager.zoomOut();
22022
22193
  }
22023
22194
  setZoom(level) {
22195
+ Logger.log('[pc-editor] setZoom', level);
22024
22196
  if (!this._isReady)
22025
22197
  return;
22026
22198
  this.canvasManager.setZoom(level);
@@ -22057,6 +22229,7 @@ class PCEditor extends EventEmitter {
22057
22229
  return this.canvasManager.getContentOffset();
22058
22230
  }
22059
22231
  fitToWidth() {
22232
+ Logger.log('[pc-editor] fitToWidth');
22060
22233
  if (!this._isReady)
22061
22234
  return;
22062
22235
  this.canvasManager.fitToWidth();
@@ -22065,23 +22238,27 @@ class PCEditor extends EventEmitter {
22065
22238
  * Force a re-render of the canvas.
22066
22239
  */
22067
22240
  render() {
22241
+ Logger.log('[pc-editor] render');
22068
22242
  if (!this._isReady)
22069
22243
  return;
22070
22244
  this.canvasManager.render();
22071
22245
  }
22072
22246
  fitToPage() {
22247
+ Logger.log('[pc-editor] fitToPage');
22073
22248
  if (!this._isReady)
22074
22249
  return;
22075
22250
  this.canvasManager.fitToPage();
22076
22251
  }
22077
22252
  // Layout control methods
22078
22253
  setAutoFlow(enabled) {
22254
+ Logger.log('[pc-editor] setAutoFlow', enabled);
22079
22255
  if (!this._isReady) {
22080
22256
  throw new Error('Editor is not ready');
22081
22257
  }
22082
22258
  this.layoutEngine.setAutoFlow(enabled);
22083
22259
  }
22084
22260
  reflowDocument() {
22261
+ Logger.log('[pc-editor] reflowDocument');
22085
22262
  if (!this._isReady) {
22086
22263
  throw new Error('Editor is not ready');
22087
22264
  }
@@ -22123,6 +22300,7 @@ class PCEditor extends EventEmitter {
22123
22300
  };
22124
22301
  }
22125
22302
  addPage() {
22303
+ Logger.log('[pc-editor] addPage');
22126
22304
  if (!this._isReady) {
22127
22305
  throw new Error('Editor is not ready');
22128
22306
  }
@@ -22134,6 +22312,7 @@ class PCEditor extends EventEmitter {
22134
22312
  this.canvasManager.setDocument(this.document);
22135
22313
  }
22136
22314
  removePage(pageId) {
22315
+ Logger.log('[pc-editor] removePage', pageId);
22137
22316
  if (!this._isReady) {
22138
22317
  throw new Error('Editor is not ready');
22139
22318
  }
@@ -22208,7 +22387,7 @@ class PCEditor extends EventEmitter {
22208
22387
  }
22209
22388
  // Use the unified focus system to get the currently focused control
22210
22389
  const focusedControl = this.canvasManager.getFocusedControl();
22211
- console.log('[PCEditor.handleKeyDown] Key:', e.key, 'focusedControl:', focusedControl?.constructor?.name);
22390
+ Logger.log('[pc-editor:handleKeyDown] Key:', e.key, 'focusedControl:', focusedControl?.constructor?.name);
22212
22391
  if (!focusedControl)
22213
22392
  return;
22214
22393
  // Vertical navigation needs layout context - handle specially
@@ -22232,9 +22411,9 @@ class PCEditor extends EventEmitter {
22232
22411
  }
22233
22412
  }
22234
22413
  // Delegate to the focused control's handleKeyDown
22235
- console.log('[PCEditor.handleKeyDown] Calling focusedControl.handleKeyDown');
22414
+ Logger.log('[pc-editor:handleKeyDown] Calling focusedControl.handleKeyDown');
22236
22415
  const handled = focusedControl.handleKeyDown(e);
22237
- console.log('[PCEditor.handleKeyDown] handled:', handled);
22416
+ Logger.log('[pc-editor:handleKeyDown] handled:', handled);
22238
22417
  if (handled) {
22239
22418
  this.canvasManager.render();
22240
22419
  // Handle text box-specific post-processing
@@ -22598,6 +22777,7 @@ class PCEditor extends EventEmitter {
22598
22777
  * This is useful when UI controls have stolen focus.
22599
22778
  */
22600
22779
  applyFormattingWithFallback(start, end, formatting) {
22780
+ Logger.log('[pc-editor] applyFormattingWithFallback', start, end, formatting);
22601
22781
  // Try current context first
22602
22782
  let flowingContent = this.getEditingFlowingContent();
22603
22783
  // Fall back to saved context
@@ -22798,6 +22978,7 @@ class PCEditor extends EventEmitter {
22798
22978
  * Works for body text, text boxes, and table cells.
22799
22979
  */
22800
22980
  setUnifiedAlignment(alignment) {
22981
+ Logger.log('[pc-editor] setUnifiedAlignment', alignment);
22801
22982
  const flowingContent = this.getEditingFlowingContent();
22802
22983
  if (!flowingContent) {
22803
22984
  throw new Error('No text is being edited');
@@ -22814,6 +22995,7 @@ class PCEditor extends EventEmitter {
22814
22995
  this.canvasManager.render();
22815
22996
  }
22816
22997
  insertText(text) {
22998
+ Logger.log('[pc-editor] insertText', text);
22817
22999
  if (!this._isReady) {
22818
23000
  throw new Error('Editor is not ready');
22819
23001
  }
@@ -22830,6 +23012,7 @@ class PCEditor extends EventEmitter {
22830
23012
  return flowingContent ? flowingContent.getText() : '';
22831
23013
  }
22832
23014
  setFlowingText(text) {
23015
+ Logger.log('[pc-editor] setFlowingText');
22833
23016
  if (!this._isReady) {
22834
23017
  throw new Error('Editor is not ready');
22835
23018
  }
@@ -22843,6 +23026,7 @@ class PCEditor extends EventEmitter {
22843
23026
  * Works for body, header, footer, text boxes, and table cells.
22844
23027
  */
22845
23028
  setCursorPosition(position) {
23029
+ Logger.log('[pc-editor] setCursorPosition', position);
22846
23030
  if (!this._isReady) {
22847
23031
  throw new Error('Editor is not ready');
22848
23032
  }
@@ -22888,6 +23072,7 @@ class PCEditor extends EventEmitter {
22888
23072
  * Works for body, header, footer, text boxes, and table cells.
22889
23073
  */
22890
23074
  insertEmbeddedObject(object, position = 'inline') {
23075
+ Logger.log('[pc-editor] insertEmbeddedObject', object.id, position);
22891
23076
  if (!this._isReady) {
22892
23077
  throw new Error('Editor is not ready');
22893
23078
  }
@@ -22914,6 +23099,7 @@ class PCEditor extends EventEmitter {
22914
23099
  * Works for body, header, footer, text boxes, and table cells.
22915
23100
  */
22916
23101
  insertSubstitutionField(fieldName, config) {
23102
+ Logger.log('[pc-editor] insertSubstitutionField', fieldName);
22917
23103
  if (!this._isReady) {
22918
23104
  throw new Error('Editor is not ready');
22919
23105
  }
@@ -22939,6 +23125,7 @@ class PCEditor extends EventEmitter {
22939
23125
  * @param displayFormat Optional format string (e.g., "Page %d" where %d is replaced by page number)
22940
23126
  */
22941
23127
  insertPageNumberField(displayFormat) {
23128
+ Logger.log('[pc-editor] insertPageNumberField');
22942
23129
  if (!this._isReady) {
22943
23130
  throw new Error('Editor is not ready');
22944
23131
  }
@@ -22964,6 +23151,7 @@ class PCEditor extends EventEmitter {
22964
23151
  * @param displayFormat Optional format string (e.g., "of %d" where %d is replaced by page count)
22965
23152
  */
22966
23153
  insertPageCountField(displayFormat) {
23154
+ Logger.log('[pc-editor] insertPageCountField');
22967
23155
  if (!this._isReady) {
22968
23156
  throw new Error('Editor is not ready');
22969
23157
  }
@@ -22989,6 +23177,7 @@ class PCEditor extends EventEmitter {
22989
23177
  * or table cells are not recommended as these don't span pages.
22990
23178
  */
22991
23179
  insertPageBreak() {
23180
+ Logger.log('[pc-editor] insertPageBreak');
22992
23181
  if (!this._isReady) {
22993
23182
  throw new Error('Editor is not ready');
22994
23183
  }
@@ -23137,7 +23326,7 @@ class PCEditor extends EventEmitter {
23137
23326
  // Find the text box in all flowing contents
23138
23327
  const textBox = this.findTextBoxById(textBoxId);
23139
23328
  if (!textBox) {
23140
- console.warn(`[PCEditor.updateTextBox] Text box not found: ${textBoxId}`);
23329
+ Logger.warn(`[pc-editor:updateTextBox] Text box not found: ${textBoxId}`);
23141
23330
  return false;
23142
23331
  }
23143
23332
  // Apply updates
@@ -23200,7 +23389,7 @@ class PCEditor extends EventEmitter {
23200
23389
  // Find the image in all flowing contents
23201
23390
  const image = this.findImageById(imageId);
23202
23391
  if (!image) {
23203
- console.warn(`[PCEditor.updateImage] Image not found: ${imageId}`);
23392
+ Logger.warn(`[pc-editor:updateImage] Image not found: ${imageId}`);
23204
23393
  return false;
23205
23394
  }
23206
23395
  // Apply updates
@@ -23234,7 +23423,7 @@ class PCEditor extends EventEmitter {
23234
23423
  return false;
23235
23424
  const image = this.findImageById(imageId);
23236
23425
  if (!image) {
23237
- console.warn(`[PCEditor.setImageSource] Image not found: ${imageId}`);
23426
+ Logger.warn(`[pc-editor:setImageSource] Image not found: ${imageId}`);
23238
23427
  return false;
23239
23428
  }
23240
23429
  image.setSource(dataUrl, options);
@@ -23314,6 +23503,39 @@ class PCEditor extends EventEmitter {
23314
23503
  table.removeColumn(colIndex);
23315
23504
  this.canvasManager.render();
23316
23505
  }
23506
+ /**
23507
+ * Merge selected cells in a table.
23508
+ * Uses the table's current cell selection range.
23509
+ * @param table The table containing the cells to merge
23510
+ * @returns true if cells were merged successfully
23511
+ */
23512
+ tableMergeCells(table) {
23513
+ Logger.log('[pc-editor] tableMergeCells');
23514
+ if (!this._isReady)
23515
+ return false;
23516
+ const result = table.mergeCells();
23517
+ if (result.success) {
23518
+ this.canvasManager.render();
23519
+ }
23520
+ return result.success;
23521
+ }
23522
+ /**
23523
+ * Split a merged cell back into individual cells.
23524
+ * @param table The table containing the merged cell
23525
+ * @param row Row index of the merged cell
23526
+ * @param col Column index of the merged cell
23527
+ * @returns true if the cell was split successfully
23528
+ */
23529
+ tableSplitCell(table, row, col) {
23530
+ Logger.log('[pc-editor] tableSplitCell', row, col);
23531
+ if (!this._isReady)
23532
+ return false;
23533
+ const result = table.splitCell(row, col);
23534
+ if (result.success) {
23535
+ this.canvasManager.render();
23536
+ }
23537
+ return result.success;
23538
+ }
23317
23539
  /**
23318
23540
  * Begin a compound operation. Groups multiple mutations into a single undo entry.
23319
23541
  * Call endCompoundOperation after making changes.
@@ -23361,7 +23583,7 @@ class PCEditor extends EventEmitter {
23361
23583
  * @deprecated Use insertEmbeddedObject instead
23362
23584
  */
23363
23585
  insertInlineElement(_elementData, _position = 'inline') {
23364
- console.warn('insertInlineElement is deprecated and no longer functional. Use insertEmbeddedObject instead.');
23586
+ Logger.warn('[pc-editor] insertInlineElement is deprecated and no longer functional. Use insertEmbeddedObject instead.');
23365
23587
  }
23366
23588
  /**
23367
23589
  * Apply merge data to substitute all substitution fields with their values.
@@ -24086,18 +24308,37 @@ class PCEditor extends EventEmitter {
24086
24308
  return this.document.bodyFlowingContent.getParagraphBoundaries();
24087
24309
  }
24088
24310
  /**
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)
24311
+ * Create a repeating section.
24312
+ *
24313
+ * If a table is currently being edited (focused), creates a table row loop
24314
+ * based on the focused cell's row. In this case, startIndex and endIndex
24315
+ * are ignored — the focused cell's row determines the loop range.
24316
+ *
24317
+ * Otherwise, creates a body text repeating section at the given paragraph boundaries.
24318
+ *
24319
+ * @param startIndex Text index at paragraph start (ignored for table row loops)
24320
+ * @param endIndex Text index at closing paragraph start (ignored for table row loops)
24093
24321
  * @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
24322
+ * @returns The created section/loop, or null if creation failed
24095
24323
  */
24096
24324
  createRepeatingSection(startIndex, endIndex, fieldPath) {
24097
24325
  if (!this._isReady) {
24098
24326
  throw new Error('Editor is not ready');
24099
24327
  }
24328
+ // If a table is focused, create a row loop instead of a text repeating section
24329
+ const focusedTable = this.getFocusedTable();
24330
+ if (focusedTable && focusedTable.focusedCell) {
24331
+ Logger.log('[pc-editor] createRepeatingSection → table row loop', fieldPath);
24332
+ const row = focusedTable.focusedCell.row;
24333
+ const loop = focusedTable.createRowLoop(row, row, fieldPath);
24334
+ if (loop) {
24335
+ this.canvasManager.render();
24336
+ this.emit('table-row-loop-added', { table: focusedTable, loop });
24337
+ }
24338
+ return null; // Row loops are not RepeatingSections, return null
24339
+ }
24100
24340
  // Repeating sections only work in body (document-level)
24341
+ Logger.log('[pc-editor] createRepeatingSection', startIndex, endIndex, fieldPath);
24101
24342
  const section = this.document.bodyFlowingContent.createRepeatingSection(startIndex, endIndex, fieldPath);
24102
24343
  if (section) {
24103
24344
  this.canvasManager.render();
@@ -24475,7 +24716,7 @@ class PCEditor extends EventEmitter {
24475
24716
  createEmbeddedObjectFromData(data) {
24476
24717
  const object = EmbeddedObjectFactory.tryCreate(data);
24477
24718
  if (!object) {
24478
- console.warn('Unknown object type:', data.objectType);
24719
+ Logger.warn('[pc-editor] Unknown object type:', data.objectType);
24479
24720
  }
24480
24721
  return object;
24481
24722
  }
@@ -24504,6 +24745,13 @@ class PCEditor extends EventEmitter {
24504
24745
  return false;
24505
24746
  }
24506
24747
  }
24748
+ /**
24749
+ * Enable or disable verbose logging.
24750
+ * When disabled (default), only errors are logged to the console.
24751
+ */
24752
+ setLogging(enabled) {
24753
+ Logger.setEnabled(enabled);
24754
+ }
24507
24755
  destroy() {
24508
24756
  this.disableTextInput();
24509
24757
  if (this.canvasManager) {
@@ -25646,33 +25894,38 @@ class DocumentInfoPane extends BasePane {
25646
25894
  }
25647
25895
  createContent() {
25648
25896
  const container = document.createElement('div');
25649
- container.className = 'pc-pane-info-list';
25897
+ container.className = 'pc-pane-label-value-grid';
25650
25898
  // Page count
25651
- const countRow = this.createInfoRow('Pages', '0');
25652
- this.pageCountEl = countRow.querySelector('.pc-pane-info-value');
25653
- container.appendChild(countRow);
25899
+ container.appendChild(this.createLabel('Pages:'));
25900
+ this.pageCountEl = this.createValue('0');
25901
+ container.appendChild(this.pageCountEl);
25902
+ container.appendChild(this.createSpacer());
25654
25903
  // Page size
25655
- const sizeRow = this.createInfoRow('Size', '-');
25656
- this.pageSizeEl = sizeRow.querySelector('.pc-pane-info-value');
25657
- container.appendChild(sizeRow);
25904
+ container.appendChild(this.createLabel('Size:'));
25905
+ this.pageSizeEl = this.createValue('-');
25906
+ container.appendChild(this.pageSizeEl);
25907
+ container.appendChild(this.createSpacer());
25658
25908
  // Page orientation
25659
- const orientationRow = this.createInfoRow('Orientation', '-');
25660
- this.pageOrientationEl = orientationRow.querySelector('.pc-pane-info-value');
25661
- container.appendChild(orientationRow);
25909
+ container.appendChild(this.createLabel('Orientation:'));
25910
+ this.pageOrientationEl = this.createValue('-');
25911
+ container.appendChild(this.pageOrientationEl);
25912
+ container.appendChild(this.createSpacer());
25662
25913
  return container;
25663
25914
  }
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;
25915
+ createLabel(text) {
25916
+ const label = document.createElement('span');
25917
+ label.className = 'pc-pane-label pc-pane-margin-label';
25918
+ label.textContent = text;
25919
+ return label;
25920
+ }
25921
+ createValue(text) {
25922
+ const value = document.createElement('span');
25923
+ value.className = 'pc-pane-info-value';
25924
+ value.textContent = text;
25925
+ return value;
25926
+ }
25927
+ createSpacer() {
25928
+ return document.createElement('div');
25676
25929
  }
25677
25930
  /**
25678
25931
  * Update the displayed information from the editor.
@@ -25852,24 +26105,48 @@ class DocumentSettingsPane extends BasePane {
25852
26105
  const container = document.createElement('div');
25853
26106
  // Margins section
25854
26107
  const marginsSection = this.createSection('Margins (mm)');
26108
+ // Five-column grid: label, edit, label, edit, stretch
25855
26109
  const marginsGrid = document.createElement('div');
25856
- marginsGrid.className = 'pc-pane-margins-grid';
26110
+ marginsGrid.className = 'pc-pane-margins-grid-5col';
25857
26111
  this.marginTopInput = this.createNumberInput({ min: 5, max: 50, step: 0.5, value: 20 });
25858
26112
  this.marginRightInput = this.createNumberInput({ min: 5, max: 50, step: 0.5, value: 20 });
25859
26113
  this.marginBottomInput = this.createNumberInput({ min: 5, max: 50, step: 0.5, value: 20 });
25860
26114
  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 }));
26115
+ // Apply margins on blur
26116
+ const applyMargins = () => this.applyMargins();
26117
+ this.marginTopInput.addEventListener('blur', applyMargins);
26118
+ this.marginRightInput.addEventListener('blur', applyMargins);
26119
+ this.marginBottomInput.addEventListener('blur', applyMargins);
26120
+ this.marginLeftInput.addEventListener('blur', applyMargins);
26121
+ this.eventCleanup.push(() => {
26122
+ this.marginTopInput?.removeEventListener('blur', applyMargins);
26123
+ this.marginRightInput?.removeEventListener('blur', applyMargins);
26124
+ this.marginBottomInput?.removeEventListener('blur', applyMargins);
26125
+ this.marginLeftInput?.removeEventListener('blur', applyMargins);
26126
+ });
26127
+ // Row 1: Top / Right
26128
+ const topLabel = this.createMarginLabel('Top:');
26129
+ const rightLabel = this.createMarginLabel('Right:');
26130
+ marginsGrid.appendChild(topLabel);
26131
+ marginsGrid.appendChild(this.marginTopInput);
26132
+ marginsGrid.appendChild(rightLabel);
26133
+ marginsGrid.appendChild(this.marginRightInput);
26134
+ marginsGrid.appendChild(this.createSpacer());
26135
+ // Row 2: Bottom / Left
26136
+ const bottomLabel = this.createMarginLabel('Bottom:');
26137
+ const leftLabel = this.createMarginLabel('Left:');
26138
+ marginsGrid.appendChild(bottomLabel);
26139
+ marginsGrid.appendChild(this.marginBottomInput);
26140
+ marginsGrid.appendChild(leftLabel);
26141
+ marginsGrid.appendChild(this.marginLeftInput);
26142
+ marginsGrid.appendChild(this.createSpacer());
25865
26143
  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
26144
  container.appendChild(marginsSection);
25871
- // Page size section
25872
- const pageSizeSection = this.createSection();
26145
+ // Page settings section using label-value grid: label, value, stretch
26146
+ const pageSection = this.createSection();
26147
+ const pageGrid = document.createElement('div');
26148
+ pageGrid.className = 'pc-pane-label-value-grid';
26149
+ // Page Size
25873
26150
  this.pageSizeSelect = this.createSelect([
25874
26151
  { value: 'A4', label: 'A4' },
25875
26152
  { value: 'Letter', label: 'Letter' },
@@ -25877,19 +26154,32 @@ class DocumentSettingsPane extends BasePane {
25877
26154
  { value: 'A3', label: 'A3' }
25878
26155
  ], 'A4');
25879
26156
  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();
26157
+ pageGrid.appendChild(this.createMarginLabel('Page Size:'));
26158
+ pageGrid.appendChild(this.pageSizeSelect);
26159
+ pageGrid.appendChild(this.createSpacer());
26160
+ // Orientation
25884
26161
  this.orientationSelect = this.createSelect([
25885
26162
  { value: 'portrait', label: 'Portrait' },
25886
26163
  { value: 'landscape', label: 'Landscape' }
25887
26164
  ], 'portrait');
25888
26165
  this.addImmediateApplyListener(this.orientationSelect, () => this.applyPageSettings());
25889
- orientationSection.appendChild(this.createFormGroup('Orientation', this.orientationSelect));
25890
- container.appendChild(orientationSection);
26166
+ pageGrid.appendChild(this.createMarginLabel('Orientation:'));
26167
+ pageGrid.appendChild(this.orientationSelect);
26168
+ pageGrid.appendChild(this.createSpacer());
26169
+ pageSection.appendChild(pageGrid);
26170
+ container.appendChild(pageSection);
25891
26171
  return container;
25892
26172
  }
26173
+ createMarginLabel(text) {
26174
+ const label = document.createElement('label');
26175
+ label.className = 'pc-pane-label pc-pane-margin-label';
26176
+ label.textContent = text;
26177
+ return label;
26178
+ }
26179
+ createSpacer() {
26180
+ const spacer = document.createElement('div');
26181
+ return spacer;
26182
+ }
25893
26183
  loadSettings() {
25894
26184
  if (!this.editor)
25895
26185
  return;
@@ -26288,9 +26578,15 @@ class FormattingPane extends BasePane {
26288
26578
  this.bulletListBtn?.classList.toggle('pc-pane-button--active', listFormatting.listType === 'bullet');
26289
26579
  this.numberedListBtn?.classList.toggle('pc-pane-button--active', listFormatting.listType === 'number');
26290
26580
  }
26581
+ else {
26582
+ this.bulletListBtn?.classList.remove('pc-pane-button--active');
26583
+ this.numberedListBtn?.classList.remove('pc-pane-button--active');
26584
+ }
26291
26585
  }
26292
26586
  catch {
26293
26587
  // No text editing active
26588
+ this.bulletListBtn?.classList.remove('pc-pane-button--active');
26589
+ this.numberedListBtn?.classList.remove('pc-pane-button--active');
26294
26590
  }
26295
26591
  }
26296
26592
  getSelection() {
@@ -26429,6 +26725,7 @@ class HyperlinkPane extends BasePane {
26429
26725
  this.titleInput = null;
26430
26726
  this.rangeHint = null;
26431
26727
  this.currentHyperlink = null;
26728
+ this._isUpdating = false;
26432
26729
  this.onApply = options.onApply;
26433
26730
  this.onRemove = options.onRemove;
26434
26731
  }
@@ -26470,15 +26767,21 @@ class HyperlinkPane extends BasePane {
26470
26767
  return container;
26471
26768
  }
26472
26769
  updateFromCursor() {
26473
- if (!this.editor)
26770
+ if (!this.editor || this._isUpdating)
26474
26771
  return;
26475
- const cursorPos = this.editor.getCursorPosition();
26476
- const hyperlink = this.editor.getHyperlinkAt(cursorPos);
26477
- if (hyperlink) {
26478
- this.showHyperlink(hyperlink);
26772
+ this._isUpdating = true;
26773
+ try {
26774
+ const cursorPos = this.editor.getCursorPosition();
26775
+ const hyperlink = this.editor.getHyperlinkAt(cursorPos);
26776
+ if (hyperlink) {
26777
+ this.showHyperlink(hyperlink);
26778
+ }
26779
+ else {
26780
+ this.hideHyperlink();
26781
+ }
26479
26782
  }
26480
- else {
26481
- this.hideHyperlink();
26783
+ finally {
26784
+ this._isUpdating = false;
26482
26785
  }
26483
26786
  }
26484
26787
  showHyperlink(hyperlink) {
@@ -27097,6 +27400,7 @@ class TextBoxPane extends BasePane {
27097
27400
  this.borderColorInput = null;
27098
27401
  this.borderStyleSelect = null;
27099
27402
  this.paddingInput = null;
27403
+ this._isUpdating = false;
27100
27404
  this.currentTextBox = null;
27101
27405
  this.onApplyCallback = options.onApply;
27102
27406
  }
@@ -27170,14 +27474,20 @@ class TextBoxPane extends BasePane {
27170
27474
  return container;
27171
27475
  }
27172
27476
  updateFromSelection() {
27173
- if (!this.editor)
27477
+ if (!this.editor || this._isUpdating)
27174
27478
  return;
27175
- const textBox = this.editor.getSelectedTextBox?.();
27176
- if (textBox && !textBox.editing) {
27177
- this.showTextBox(textBox);
27479
+ this._isUpdating = true;
27480
+ try {
27481
+ const textBox = this.editor.getSelectedTextBox?.();
27482
+ if (textBox && !textBox.editing) {
27483
+ this.showTextBox(textBox);
27484
+ }
27485
+ else {
27486
+ this.hideTextBox();
27487
+ }
27178
27488
  }
27179
- else {
27180
- this.hideTextBox();
27489
+ finally {
27490
+ this._isUpdating = false;
27181
27491
  }
27182
27492
  }
27183
27493
  /**
@@ -27331,6 +27641,7 @@ class ImagePane extends BasePane {
27331
27641
  this.altTextInput = null;
27332
27642
  this.fileInput = null;
27333
27643
  this.currentImage = null;
27644
+ this._isUpdating = false;
27334
27645
  this.maxImageWidth = options.maxImageWidth ?? 400;
27335
27646
  this.maxImageHeight = options.maxImageHeight ?? 400;
27336
27647
  this.onApplyCallback = options.onApply;
@@ -27412,14 +27723,20 @@ class ImagePane extends BasePane {
27412
27723
  return container;
27413
27724
  }
27414
27725
  updateFromSelection() {
27415
- if (!this.editor)
27726
+ if (!this.editor || this._isUpdating)
27416
27727
  return;
27417
- const image = this.editor.getSelectedImage?.();
27418
- if (image) {
27419
- this.showImage(image);
27728
+ this._isUpdating = true;
27729
+ try {
27730
+ const image = this.editor.getSelectedImage?.();
27731
+ if (image) {
27732
+ this.showImage(image);
27733
+ }
27734
+ else {
27735
+ this.hideImage();
27736
+ }
27420
27737
  }
27421
- else {
27422
- this.hideImage();
27738
+ finally {
27739
+ this._isUpdating = false;
27423
27740
  }
27424
27741
  }
27425
27742
  /**
@@ -27584,6 +27901,9 @@ class TablePane extends BasePane {
27584
27901
  // Default controls
27585
27902
  this.defaultPaddingInput = null;
27586
27903
  this.defaultBorderColorInput = null;
27904
+ // Merge/split buttons
27905
+ this.mergeCellsBtn = null;
27906
+ this.splitCellBtn = null;
27587
27907
  // Cell formatting controls
27588
27908
  this.cellBgColorInput = null;
27589
27909
  this.borderTopCheck = null;
@@ -27594,6 +27914,7 @@ class TablePane extends BasePane {
27594
27914
  this.borderColorInput = null;
27595
27915
  this.borderStyleSelect = null;
27596
27916
  this.currentTable = null;
27917
+ this._isUpdating = false;
27597
27918
  this.onApplyCallback = options.onApply;
27598
27919
  }
27599
27920
  attach(options) {
@@ -27602,12 +27923,12 @@ class TablePane extends BasePane {
27602
27923
  // Listen for selection/focus changes
27603
27924
  const updateHandler = () => this.updateFromFocusedTable();
27604
27925
  this.editor.on('selection-change', updateHandler);
27605
- this.editor.on('table-cell-focus', updateHandler);
27606
- this.editor.on('table-cell-selection', updateHandler);
27926
+ this.editor.on('tablecell-cursor-changed', updateHandler);
27927
+ this.editor.on('table-cell-selection-changed', updateHandler);
27607
27928
  this.eventCleanup.push(() => {
27608
27929
  this.editor?.off('selection-change', updateHandler);
27609
- this.editor?.off('table-cell-focus', updateHandler);
27610
- this.editor?.off('table-cell-selection', updateHandler);
27930
+ this.editor?.off('tablecell-cursor-changed', updateHandler);
27931
+ this.editor?.off('table-cell-selection-changed', updateHandler);
27611
27932
  });
27612
27933
  // Initial update
27613
27934
  this.updateFromFocusedTable();
@@ -27676,6 +27997,17 @@ class TablePane extends BasePane {
27676
27997
  const cellSection = this.createSection('Cell Formatting');
27677
27998
  this.cellSelectionDisplay = this.createHint('No cell selected');
27678
27999
  cellSection.appendChild(this.cellSelectionDisplay);
28000
+ // Merge/Split buttons
28001
+ const mergeBtnGroup = this.createButtonGroup();
28002
+ this.mergeCellsBtn = this.createButton('Merge Cells');
28003
+ this.mergeCellsBtn.disabled = true;
28004
+ this.splitCellBtn = this.createButton('Split Cell');
28005
+ this.splitCellBtn.disabled = true;
28006
+ this.addButtonListener(this.mergeCellsBtn, () => this.doMergeCells());
28007
+ this.addButtonListener(this.splitCellBtn, () => this.doSplitCell());
28008
+ mergeBtnGroup.appendChild(this.mergeCellsBtn);
28009
+ mergeBtnGroup.appendChild(this.splitCellBtn);
28010
+ cellSection.appendChild(mergeBtnGroup);
27679
28011
  // Background
27680
28012
  this.cellBgColorInput = this.createColorInput('#ffffff');
27681
28013
  cellSection.appendChild(this.createFormGroup('Background', this.cellBgColorInput));
@@ -27732,14 +28064,20 @@ class TablePane extends BasePane {
27732
28064
  return container;
27733
28065
  }
27734
28066
  updateFromFocusedTable() {
27735
- if (!this.editor)
28067
+ if (!this.editor || this._isUpdating)
27736
28068
  return;
27737
- const table = this.editor.getFocusedTable();
27738
- if (table) {
27739
- this.showTable(table);
28069
+ this._isUpdating = true;
28070
+ try {
28071
+ const table = this.editor.getFocusedTable();
28072
+ if (table) {
28073
+ this.showTable(table);
28074
+ }
28075
+ else {
28076
+ this.hideTable();
28077
+ }
27740
28078
  }
27741
- else {
27742
- this.hideTable();
28079
+ finally {
28080
+ this._isUpdating = false;
27743
28081
  }
27744
28082
  }
27745
28083
  /**
@@ -27786,6 +28124,15 @@ class TablePane extends BasePane {
27786
28124
  return;
27787
28125
  const focusedCell = table.focusedCell;
27788
28126
  const selectedRange = table.selectedRange;
28127
+ // Update merge/split button states
28128
+ if (this.mergeCellsBtn) {
28129
+ const canMerge = selectedRange ? table.canMergeRange(selectedRange).canMerge : false;
28130
+ this.mergeCellsBtn.disabled = !canMerge;
28131
+ }
28132
+ if (this.splitCellBtn) {
28133
+ const canSplit = focusedCell ? table.canSplitCell(focusedCell.row, focusedCell.col).canSplit : false;
28134
+ this.splitCellBtn.disabled = !canSplit;
28135
+ }
27789
28136
  if (selectedRange) {
27790
28137
  const count = (selectedRange.end.row - selectedRange.start.row + 1) *
27791
28138
  (selectedRange.end.col - selectedRange.start.col + 1);
@@ -27943,6 +28290,21 @@ class TablePane extends BasePane {
27943
28290
  hasTable() {
27944
28291
  return this.currentTable !== null;
27945
28292
  }
28293
+ doMergeCells() {
28294
+ if (!this.editor || !this.currentTable)
28295
+ return;
28296
+ this.editor.tableMergeCells(this.currentTable);
28297
+ this.updateFromFocusedTable();
28298
+ }
28299
+ doSplitCell() {
28300
+ if (!this.editor || !this.currentTable)
28301
+ return;
28302
+ const focused = this.currentTable.focusedCell;
28303
+ if (!focused)
28304
+ return;
28305
+ this.editor.tableSplitCell(this.currentTable, focused.row, focused.col);
28306
+ this.updateFromFocusedTable();
28307
+ }
27946
28308
  /**
27947
28309
  * Update the pane from current editor state.
27948
28310
  */
@@ -27975,6 +28337,7 @@ exports.HtmlConverter = HtmlConverter;
27975
28337
  exports.HyperlinkPane = HyperlinkPane;
27976
28338
  exports.ImageObject = ImageObject;
27977
28339
  exports.ImagePane = ImagePane;
28340
+ exports.Logger = Logger;
27978
28341
  exports.MergeDataPane = MergeDataPane;
27979
28342
  exports.PCEditor = PCEditor;
27980
28343
  exports.PDFImportError = PDFImportError;