@productcloudos/editor 1.0.5 → 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.
package/dist/pc-editor.js CHANGED
@@ -7758,6 +7758,9 @@ class TableObject extends BaseEmbeddedObject {
7758
7758
  result.mergedCell.markReflowDirty();
7759
7759
  }
7760
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);
7761
7764
  this.emit('cells-merged', { range: mergeRange });
7762
7765
  this.emit('content-changed', {});
7763
7766
  }
@@ -14314,7 +14317,8 @@ class CanvasManager extends EventEmitter {
14314
14317
  const pageIndex = this.document.pages.findIndex(p => p.id === pageId);
14315
14318
  // Get the slice for this page (for multi-page tables)
14316
14319
  const slice = table.getRenderedSlice(pageIndex);
14317
- const tablePosition = slice?.position || table.renderedPosition;
14320
+ const tablePosition = slice?.position ||
14321
+ (table.renderedPageIndex === pageIndex ? table.renderedPosition : null);
14318
14322
  const sliceHeight = slice?.height || table.height;
14319
14323
  // Check if point is within the table slice on this page
14320
14324
  const isInsideTable = tablePosition &&
@@ -14347,6 +14351,7 @@ class CanvasManager extends EventEmitter {
14347
14351
  end: cellAddr
14348
14352
  });
14349
14353
  this.render();
14354
+ this.emit('table-cell-selection-changed', { table });
14350
14355
  e.preventDefault();
14351
14356
  return;
14352
14357
  }
@@ -14626,7 +14631,8 @@ class CanvasManager extends EventEmitter {
14626
14631
  const currentPageIndex = this.document.pages.findIndex(p => p.id === pageId);
14627
14632
  // Get the slice for the current page (for multi-page tables)
14628
14633
  const slice = table.getRenderedSlice(currentPageIndex);
14629
- const tablePosition = slice?.position || table.renderedPosition;
14634
+ const tablePosition = slice?.position ||
14635
+ (table.renderedPageIndex === currentPageIndex ? table.renderedPosition : null);
14630
14636
  const sliceHeight = slice?.height || table.height;
14631
14637
  if (tablePosition) {
14632
14638
  // Check if point is within the table slice on this page
@@ -14656,6 +14662,7 @@ class CanvasManager extends EventEmitter {
14656
14662
  end: cellAddr
14657
14663
  });
14658
14664
  this.render();
14665
+ this.emit('table-cell-selection-changed', { table });
14659
14666
  }
14660
14667
  }
14661
14668
  }
@@ -15199,40 +15206,46 @@ class CanvasManager extends EventEmitter {
15199
15206
  canvas.style.cursor = 'move';
15200
15207
  return;
15201
15208
  }
15202
- // Show text cursor for text boxes
15203
- if (object instanceof TextBoxObject) {
15204
- canvas.style.cursor = 'text';
15205
- 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;
15212
+ }
15213
+ else if (object instanceof TableObject && this._focusedControl === object) {
15214
+ canvas.style.cursor = CanvasManager.TEXT_CURSOR;
15206
15215
  }
15216
+ else {
15217
+ canvas.style.cursor = 'default';
15218
+ }
15219
+ return;
15207
15220
  }
15208
15221
  }
15209
15222
  // Check for table cells (show text cursor)
15210
15223
  const tableCellHit = hitTestManager.queryByType(pageIndex, point, 'table-cell');
15211
15224
  if (tableCellHit && tableCellHit.data.type === 'table-cell') {
15212
- canvas.style.cursor = 'text';
15225
+ canvas.style.cursor = CanvasManager.TEXT_CURSOR;
15213
15226
  return;
15214
15227
  }
15215
15228
  // Check for text regions (body, header, footer - show text cursor)
15216
15229
  const textRegionHit = hitTestManager.queryByType(pageIndex, point, 'text-region');
15217
15230
  if (textRegionHit && textRegionHit.data.type === 'text-region') {
15218
- canvas.style.cursor = 'text';
15231
+ canvas.style.cursor = CanvasManager.TEXT_CURSOR;
15219
15232
  return;
15220
15233
  }
15221
15234
  // Also check if point is within any editable region (body, header, footer)
15222
15235
  // This catches cases where text region hit targets may not cover empty space
15223
15236
  const bodyRegion = this.regionManager.getBodyRegion();
15224
15237
  if (bodyRegion && bodyRegion.containsPointInRegion(point, pageIndex)) {
15225
- canvas.style.cursor = 'text';
15238
+ canvas.style.cursor = CanvasManager.TEXT_CURSOR;
15226
15239
  return;
15227
15240
  }
15228
15241
  const headerRegion = this.regionManager.getHeaderRegion();
15229
15242
  if (headerRegion && headerRegion.containsPointInRegion(point, pageIndex)) {
15230
- canvas.style.cursor = 'text';
15243
+ canvas.style.cursor = CanvasManager.TEXT_CURSOR;
15231
15244
  return;
15232
15245
  }
15233
15246
  const footerRegion = this.regionManager.getFooterRegion();
15234
15247
  if (footerRegion && footerRegion.containsPointInRegion(point, pageIndex)) {
15235
- canvas.style.cursor = 'text';
15248
+ canvas.style.cursor = CanvasManager.TEXT_CURSOR;
15236
15249
  return;
15237
15250
  }
15238
15251
  canvas.style.cursor = 'default';
@@ -15250,7 +15263,8 @@ class CanvasManager extends EventEmitter {
15250
15263
  const { table, dividerType, index } = target.data;
15251
15264
  // Get the table position from slice info
15252
15265
  const slice = table.getRenderedSlice(pageIndex);
15253
- const tablePosition = slice?.position || table.renderedPosition;
15266
+ const tablePosition = slice?.position ||
15267
+ (table.renderedPageIndex === pageIndex ? table.renderedPosition : null);
15254
15268
  if (tablePosition) {
15255
15269
  // Calculate the divider position based on type and index
15256
15270
  let position;
@@ -15978,7 +15992,9 @@ class CanvasManager extends EventEmitter {
15978
15992
  if (obj instanceof TableObject) {
15979
15993
  // For multi-page tables, check if this page has a rendered slice
15980
15994
  const slice = obj.getRenderedSlice(pageIndex);
15981
- 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);
15982
15998
  if (tablePosition) {
15983
15999
  // Check if point is inside the table slice on this page
15984
16000
  const sliceHeight = slice?.height || obj.height;
@@ -16222,6 +16238,10 @@ class CanvasManager extends EventEmitter {
16222
16238
  }
16223
16239
  CanvasManager.CELL_SELECTION_THRESHOLD = 5; // Minimum pixels to drag before cell selection starts
16224
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";
16225
16245
 
16226
16246
  /**
16227
16247
  * DataBinder handles binding data to documents.
@@ -21510,6 +21530,10 @@ class PCEditor extends EventEmitter {
21510
21530
  this.canvasManager.on('tablecell-cursor-changed', (data) => {
21511
21531
  this.emit('tablecell-cursor-changed', data);
21512
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
+ });
21513
21537
  this.canvasManager.on('repeating-section-clicked', (data) => {
21514
21538
  // Repeating section clicked - update selection state
21515
21539
  if (data.section && data.section.id) {
@@ -23479,6 +23503,39 @@ class PCEditor extends EventEmitter {
23479
23503
  table.removeColumn(colIndex);
23480
23504
  this.canvasManager.render();
23481
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
+ }
23482
23539
  /**
23483
23540
  * Begin a compound operation. Groups multiple mutations into a single undo entry.
23484
23541
  * Call endCompoundOperation after making changes.
@@ -26521,9 +26578,15 @@ class FormattingPane extends BasePane {
26521
26578
  this.bulletListBtn?.classList.toggle('pc-pane-button--active', listFormatting.listType === 'bullet');
26522
26579
  this.numberedListBtn?.classList.toggle('pc-pane-button--active', listFormatting.listType === 'number');
26523
26580
  }
26581
+ else {
26582
+ this.bulletListBtn?.classList.remove('pc-pane-button--active');
26583
+ this.numberedListBtn?.classList.remove('pc-pane-button--active');
26584
+ }
26524
26585
  }
26525
26586
  catch {
26526
26587
  // No text editing active
26588
+ this.bulletListBtn?.classList.remove('pc-pane-button--active');
26589
+ this.numberedListBtn?.classList.remove('pc-pane-button--active');
26527
26590
  }
26528
26591
  }
26529
26592
  getSelection() {
@@ -27838,8 +27901,9 @@ class TablePane extends BasePane {
27838
27901
  // Default controls
27839
27902
  this.defaultPaddingInput = null;
27840
27903
  this.defaultBorderColorInput = null;
27841
- // Row loop controls
27842
- this.loopFieldInput = null;
27904
+ // Merge/split buttons
27905
+ this.mergeCellsBtn = null;
27906
+ this.splitCellBtn = null;
27843
27907
  // Cell formatting controls
27844
27908
  this.cellBgColorInput = null;
27845
27909
  this.borderTopCheck = null;
@@ -27859,12 +27923,12 @@ class TablePane extends BasePane {
27859
27923
  // Listen for selection/focus changes
27860
27924
  const updateHandler = () => this.updateFromFocusedTable();
27861
27925
  this.editor.on('selection-change', updateHandler);
27862
- this.editor.on('table-cell-focus', updateHandler);
27863
- this.editor.on('table-cell-selection', updateHandler);
27926
+ this.editor.on('tablecell-cursor-changed', updateHandler);
27927
+ this.editor.on('table-cell-selection-changed', updateHandler);
27864
27928
  this.eventCleanup.push(() => {
27865
27929
  this.editor?.off('selection-change', updateHandler);
27866
- this.editor?.off('table-cell-focus', updateHandler);
27867
- this.editor?.off('table-cell-selection', updateHandler);
27930
+ this.editor?.off('tablecell-cursor-changed', updateHandler);
27931
+ this.editor?.off('table-cell-selection-changed', updateHandler);
27868
27932
  });
27869
27933
  // Initial update
27870
27934
  this.updateFromFocusedTable();
@@ -27917,16 +27981,6 @@ class TablePane extends BasePane {
27917
27981
  this.addButtonListener(applyHeadersBtn, () => this.applyHeaders());
27918
27982
  headersSection.appendChild(applyHeadersBtn);
27919
27983
  container.appendChild(headersSection);
27920
- // Row Loop section
27921
- const loopSection = this.createSection('Row Loop');
27922
- this.loopFieldInput = this.createTextInput({ placeholder: 'items' });
27923
- loopSection.appendChild(this.createFormGroup('Array Field', this.loopFieldInput, {
27924
- hint: 'Creates a loop on the currently focused row'
27925
- }));
27926
- const createLoopBtn = this.createButton('Create Row Loop');
27927
- this.addButtonListener(createLoopBtn, () => this.createRowLoop());
27928
- loopSection.appendChild(createLoopBtn);
27929
- container.appendChild(loopSection);
27930
27984
  // Defaults section
27931
27985
  const defaultsSection = this.createSection('Defaults');
27932
27986
  const defaultsRow = this.createRow();
@@ -27943,6 +27997,17 @@ class TablePane extends BasePane {
27943
27997
  const cellSection = this.createSection('Cell Formatting');
27944
27998
  this.cellSelectionDisplay = this.createHint('No cell selected');
27945
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);
27946
28011
  // Background
27947
28012
  this.cellBgColorInput = this.createColorInput('#ffffff');
27948
28013
  cellSection.appendChild(this.createFormGroup('Background', this.cellBgColorInput));
@@ -28059,6 +28124,15 @@ class TablePane extends BasePane {
28059
28124
  return;
28060
28125
  const focusedCell = table.focusedCell;
28061
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
+ }
28062
28136
  if (selectedRange) {
28063
28137
  const count = (selectedRange.end.row - selectedRange.start.row + 1) *
28064
28138
  (selectedRange.end.col - selectedRange.start.col + 1);
@@ -28216,20 +28290,20 @@ class TablePane extends BasePane {
28216
28290
  hasTable() {
28217
28291
  return this.currentTable !== null;
28218
28292
  }
28219
- createRowLoop() {
28220
- if (!this.editor || !this.currentTable) {
28221
- this.onApplyCallback?.(false, new Error('No table focused'));
28293
+ doMergeCells() {
28294
+ if (!this.editor || !this.currentTable)
28222
28295
  return;
28223
- }
28224
- const fieldPath = this.loopFieldInput?.value.trim() || '';
28225
- if (!fieldPath) {
28226
- this.onApplyCallback?.(false, new Error('Array field path is required'));
28296
+ this.editor.tableMergeCells(this.currentTable);
28297
+ this.updateFromFocusedTable();
28298
+ }
28299
+ doSplitCell() {
28300
+ if (!this.editor || !this.currentTable)
28227
28301
  return;
28228
- }
28229
- // Uses the unified createRepeatingSection API which detects
28230
- // that a table is focused and creates a row loop on the focused row
28231
- this.editor.createRepeatingSection(0, 0, fieldPath);
28232
- this.onApplyCallback?.(true);
28302
+ const focused = this.currentTable.focusedCell;
28303
+ if (!focused)
28304
+ return;
28305
+ this.editor.tableSplitCell(this.currentTable, focused.row, focused.col);
28306
+ this.updateFromFocusedTable();
28233
28307
  }
28234
28308
  /**
28235
28309
  * Update the pane from current editor state.