@productcloudos/editor 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +1 -0
  2. package/dist/pc-editor.esm.js +2777 -5
  3. package/dist/pc-editor.esm.js.map +1 -1
  4. package/dist/pc-editor.js +2789 -4
  5. package/dist/pc-editor.js.map +1 -1
  6. package/dist/pc-editor.min.js +1 -1
  7. package/dist/pc-editor.min.js.map +1 -1
  8. package/dist/types/lib/core/PCEditor.d.ts +69 -0
  9. package/dist/types/lib/core/PCEditor.d.ts.map +1 -1
  10. package/dist/types/lib/index.d.ts +2 -0
  11. package/dist/types/lib/index.d.ts.map +1 -1
  12. package/dist/types/lib/panes/BasePane.d.ts +117 -0
  13. package/dist/types/lib/panes/BasePane.d.ts.map +1 -0
  14. package/dist/types/lib/panes/DocumentInfoPane.d.ts +24 -0
  15. package/dist/types/lib/panes/DocumentInfoPane.d.ts.map +1 -0
  16. package/dist/types/lib/panes/DocumentSettingsPane.d.ts +28 -0
  17. package/dist/types/lib/panes/DocumentSettingsPane.d.ts.map +1 -0
  18. package/dist/types/lib/panes/FormattingPane.d.ts +82 -0
  19. package/dist/types/lib/panes/FormattingPane.d.ts.map +1 -0
  20. package/dist/types/lib/panes/HyperlinkPane.d.ts +66 -0
  21. package/dist/types/lib/panes/HyperlinkPane.d.ts.map +1 -0
  22. package/dist/types/lib/panes/ImagePane.d.ts +79 -0
  23. package/dist/types/lib/panes/ImagePane.d.ts.map +1 -0
  24. package/dist/types/lib/panes/MergeDataPane.d.ts +55 -0
  25. package/dist/types/lib/panes/MergeDataPane.d.ts.map +1 -0
  26. package/dist/types/lib/panes/RepeatingSectionPane.d.ts +62 -0
  27. package/dist/types/lib/panes/RepeatingSectionPane.d.ts.map +1 -0
  28. package/dist/types/lib/panes/SubstitutionFieldPane.d.ts +65 -0
  29. package/dist/types/lib/panes/SubstitutionFieldPane.d.ts.map +1 -0
  30. package/dist/types/lib/panes/TablePane.d.ts +88 -0
  31. package/dist/types/lib/panes/TablePane.d.ts.map +1 -0
  32. package/dist/types/lib/panes/TableRowLoopPane.d.ts +68 -0
  33. package/dist/types/lib/panes/TableRowLoopPane.d.ts.map +1 -0
  34. package/dist/types/lib/panes/TextBoxPane.d.ts +67 -0
  35. package/dist/types/lib/panes/TextBoxPane.d.ts.map +1 -0
  36. package/dist/types/lib/panes/ViewSettingsPane.d.ts +52 -0
  37. package/dist/types/lib/panes/ViewSettingsPane.d.ts.map +1 -0
  38. package/dist/types/lib/panes/index.d.ts +34 -0
  39. package/dist/types/lib/panes/index.d.ts.map +1 -0
  40. package/dist/types/lib/panes/types.d.ts +111 -0
  41. package/dist/types/lib/panes/types.d.ts.map +1 -0
  42. package/dist/types/lib/text/TextFormatting.d.ts +9 -0
  43. package/dist/types/lib/text/TextFormatting.d.ts.map +1 -1
  44. package/dist/types/lib/undo/transaction/MutationUndo.d.ts.map +1 -1
  45. package/package.json +1 -1
package/dist/pc-editor.js CHANGED
@@ -864,6 +864,20 @@ class TextFormattingManager extends EventEmitter {
864
864
  this.emit('formatting-changed', { start, end, formatting });
865
865
  }
866
866
  }
867
+ /**
868
+ * Set formatting at a specific position, replacing all existing formatting.
869
+ * Unlike applyFormatting which merges, this replaces completely.
870
+ * Used by undo operations to restore exact previous state.
871
+ * @param position Character position
872
+ * @param formatting Complete formatting to set
873
+ * @param silent If true, don't emit the formatting-changed event
874
+ */
875
+ setFormattingAt(position, formatting, silent = false) {
876
+ this.formatting.set(position, { ...formatting });
877
+ if (!silent) {
878
+ this.emit('formatting-changed', { start: position, end: position + 1, formatting });
879
+ }
880
+ }
867
881
  /**
868
882
  * Remove formatting from a range, reverting to default.
869
883
  */
@@ -18860,11 +18874,11 @@ class MutationUndo {
18860
18874
  const data = mutation.data;
18861
18875
  // Restore deleted text
18862
18876
  content.insertTextAt(data.position, data.deletedText);
18863
- // Restore formatting
18877
+ // Restore formatting using setFormattingAt to replace completely
18864
18878
  if (data.deletedFormatting) {
18865
18879
  const fm = content.getFormattingManager();
18866
18880
  data.deletedFormatting.forEach((style, offset) => {
18867
- fm.applyFormatting(data.position + offset, data.position + offset + 1, style);
18881
+ fm.setFormattingAt(data.position + offset, style, true);
18868
18882
  });
18869
18883
  }
18870
18884
  // Restore substitution fields
@@ -18890,9 +18904,10 @@ class MutationUndo {
18890
18904
  return;
18891
18905
  const data = mutation.data;
18892
18906
  const fm = content.getFormattingManager();
18893
- // Restore previous formatting
18907
+ // Restore previous formatting using setFormattingAt to replace completely
18908
+ // (not merge, which would leave properties like backgroundColor intact)
18894
18909
  data.previousFormatting.forEach((style, offset) => {
18895
- fm.applyFormatting(data.start + offset, data.start + offset + 1, style);
18910
+ fm.setFormattingAt(data.start + offset, style, true);
18896
18911
  });
18897
18912
  }
18898
18913
  redoFormat(mutation) {
@@ -23109,6 +23124,144 @@ class PCEditor extends EventEmitter {
23109
23124
  return null;
23110
23125
  }
23111
23126
  // ============================================
23127
+ // Text Box Update Operations
23128
+ // ============================================
23129
+ /**
23130
+ * Update properties of a text box.
23131
+ * @param textBoxId The ID of the text box to update
23132
+ * @param updates The properties to update
23133
+ */
23134
+ updateTextBox(textBoxId, updates) {
23135
+ if (!this._isReady)
23136
+ return false;
23137
+ // Find the text box in all flowing contents
23138
+ const textBox = this.findTextBoxById(textBoxId);
23139
+ if (!textBox) {
23140
+ console.warn(`[PCEditor.updateTextBox] Text box not found: ${textBoxId}`);
23141
+ return false;
23142
+ }
23143
+ // Apply updates
23144
+ if (updates.position !== undefined) {
23145
+ textBox.position = updates.position;
23146
+ }
23147
+ if (updates.relativeOffset !== undefined) {
23148
+ textBox.relativeOffset = updates.relativeOffset;
23149
+ }
23150
+ if (updates.backgroundColor !== undefined) {
23151
+ textBox.backgroundColor = updates.backgroundColor;
23152
+ }
23153
+ if (updates.border !== undefined) {
23154
+ // Merge with existing border
23155
+ const existingBorder = textBox.border;
23156
+ textBox.border = {
23157
+ top: updates.border.top || existingBorder.top,
23158
+ right: updates.border.right || existingBorder.right,
23159
+ bottom: updates.border.bottom || existingBorder.bottom,
23160
+ left: updates.border.left || existingBorder.left
23161
+ };
23162
+ }
23163
+ if (updates.padding !== undefined) {
23164
+ textBox.padding = updates.padding;
23165
+ }
23166
+ this.render();
23167
+ this.emit('textbox-updated', { textBoxId, updates });
23168
+ return true;
23169
+ }
23170
+ /**
23171
+ * Find a text box by ID across all flowing contents.
23172
+ */
23173
+ findTextBoxById(textBoxId) {
23174
+ const flowingContents = [
23175
+ this.document.bodyFlowingContent,
23176
+ this.document.headerFlowingContent,
23177
+ this.document.footerFlowingContent
23178
+ ].filter(Boolean);
23179
+ for (const flowingContent of flowingContents) {
23180
+ const embeddedObjects = flowingContent.getEmbeddedObjects();
23181
+ for (const [, obj] of embeddedObjects.entries()) {
23182
+ if (obj.id === textBoxId && obj instanceof TextBoxObject) {
23183
+ return obj;
23184
+ }
23185
+ }
23186
+ }
23187
+ return null;
23188
+ }
23189
+ // ============================================
23190
+ // Image Update Operations
23191
+ // ============================================
23192
+ /**
23193
+ * Update properties of an image.
23194
+ * @param imageId The ID of the image to update
23195
+ * @param updates The properties to update
23196
+ */
23197
+ updateImage(imageId, updates) {
23198
+ if (!this._isReady)
23199
+ return false;
23200
+ // Find the image in all flowing contents
23201
+ const image = this.findImageById(imageId);
23202
+ if (!image) {
23203
+ console.warn(`[PCEditor.updateImage] Image not found: ${imageId}`);
23204
+ return false;
23205
+ }
23206
+ // Apply updates
23207
+ if (updates.position !== undefined) {
23208
+ image.position = updates.position;
23209
+ }
23210
+ if (updates.relativeOffset !== undefined) {
23211
+ image.relativeOffset = updates.relativeOffset;
23212
+ }
23213
+ if (updates.fit !== undefined) {
23214
+ image.fit = updates.fit;
23215
+ }
23216
+ if (updates.resizeMode !== undefined) {
23217
+ image.resizeMode = updates.resizeMode;
23218
+ }
23219
+ if (updates.alt !== undefined) {
23220
+ image.alt = updates.alt;
23221
+ }
23222
+ this.render();
23223
+ this.emit('image-updated', { imageId, updates });
23224
+ return true;
23225
+ }
23226
+ /**
23227
+ * Set the source of an image.
23228
+ * @param imageId The ID of the image
23229
+ * @param dataUrl The data URL of the new image source
23230
+ * @param options Optional sizing options
23231
+ */
23232
+ setImageSource(imageId, dataUrl, options) {
23233
+ if (!this._isReady)
23234
+ return false;
23235
+ const image = this.findImageById(imageId);
23236
+ if (!image) {
23237
+ console.warn(`[PCEditor.setImageSource] Image not found: ${imageId}`);
23238
+ return false;
23239
+ }
23240
+ image.setSource(dataUrl, options);
23241
+ this.render();
23242
+ this.emit('image-source-changed', { imageId });
23243
+ return true;
23244
+ }
23245
+ /**
23246
+ * Find an image by ID across all flowing contents.
23247
+ */
23248
+ findImageById(imageId) {
23249
+ const flowingContents = [
23250
+ this.document.bodyFlowingContent,
23251
+ this.document.headerFlowingContent,
23252
+ this.document.footerFlowingContent
23253
+ ].filter(Boolean);
23254
+ for (const flowingContent of flowingContents) {
23255
+ const embeddedObjects = flowingContent.getEmbeddedObjects();
23256
+ for (const [, obj] of embeddedObjects.entries()) {
23257
+ if (obj.id === imageId && obj instanceof ImageObject) {
23258
+ return obj;
23259
+ }
23260
+ }
23261
+ }
23262
+ return null;
23263
+ }
23264
+ // ============================================
23112
23265
  // Table Structure Operations (with undo support)
23113
23266
  // ============================================
23114
23267
  /**
@@ -25179,8 +25332,2628 @@ class VerticalRuler extends RulerControl {
25179
25332
  }
25180
25333
  }
25181
25334
 
25335
+ /**
25336
+ * BasePane - Abstract base class for editor property panes.
25337
+ *
25338
+ * Panes are property editors that work with PCEditor via the public API only.
25339
+ * They are content-only (no title bar) for flexible layout by consumers.
25340
+ */
25341
+ /**
25342
+ * Abstract base class for editor panes.
25343
+ */
25344
+ class BasePane extends BaseControl {
25345
+ constructor(id, options = {}) {
25346
+ super(id, options);
25347
+ this.sectionElement = null;
25348
+ this.className = options.className || '';
25349
+ }
25350
+ /**
25351
+ * Attach the pane to an editor.
25352
+ */
25353
+ attach(options) {
25354
+ // Store the section element if provided
25355
+ this.sectionElement = options.sectionElement || null;
25356
+ super.attach(options);
25357
+ }
25358
+ /**
25359
+ * Show the pane (and section element if provided).
25360
+ */
25361
+ show() {
25362
+ this._isVisible = true;
25363
+ if (this.sectionElement) {
25364
+ this.sectionElement.style.display = '';
25365
+ }
25366
+ if (this.element) {
25367
+ this.element.style.display = '';
25368
+ this.update();
25369
+ }
25370
+ this.emit('visibility-changed', { visible: true });
25371
+ }
25372
+ /**
25373
+ * Hide the pane (and section element if provided).
25374
+ */
25375
+ hide() {
25376
+ this._isVisible = false;
25377
+ if (this.sectionElement) {
25378
+ this.sectionElement.style.display = 'none';
25379
+ }
25380
+ if (this.element) {
25381
+ this.element.style.display = 'none';
25382
+ }
25383
+ this.emit('visibility-changed', { visible: false });
25384
+ }
25385
+ /**
25386
+ * Create a form group element with label.
25387
+ */
25388
+ createFormGroup(label, inputElement, options) {
25389
+ const group = document.createElement('div');
25390
+ group.className = 'pc-pane-form-group';
25391
+ if (options?.inline) {
25392
+ group.classList.add('pc-pane-form-group--inline');
25393
+ }
25394
+ const labelEl = document.createElement('label');
25395
+ labelEl.className = 'pc-pane-label';
25396
+ labelEl.textContent = label;
25397
+ group.appendChild(labelEl);
25398
+ group.appendChild(inputElement);
25399
+ if (options?.hint) {
25400
+ const hintEl = document.createElement('span');
25401
+ hintEl.className = 'pc-pane-hint';
25402
+ hintEl.textContent = options.hint;
25403
+ group.appendChild(hintEl);
25404
+ }
25405
+ return group;
25406
+ }
25407
+ /**
25408
+ * Create a text input element.
25409
+ */
25410
+ createTextInput(options) {
25411
+ const input = document.createElement('input');
25412
+ input.type = options?.type || 'text';
25413
+ input.className = 'pc-pane-input';
25414
+ if (options?.placeholder) {
25415
+ input.placeholder = options.placeholder;
25416
+ }
25417
+ if (options?.value !== undefined) {
25418
+ input.value = options.value;
25419
+ }
25420
+ return input;
25421
+ }
25422
+ /**
25423
+ * Create a number input element.
25424
+ */
25425
+ createNumberInput(options) {
25426
+ const input = document.createElement('input');
25427
+ input.type = 'number';
25428
+ input.className = 'pc-pane-input pc-pane-input--number';
25429
+ if (options?.min !== undefined)
25430
+ input.min = String(options.min);
25431
+ if (options?.max !== undefined)
25432
+ input.max = String(options.max);
25433
+ if (options?.step !== undefined)
25434
+ input.step = String(options.step);
25435
+ if (options?.value !== undefined)
25436
+ input.value = String(options.value);
25437
+ return input;
25438
+ }
25439
+ /**
25440
+ * Create a select element with options.
25441
+ */
25442
+ createSelect(optionsList, selectedValue) {
25443
+ const select = document.createElement('select');
25444
+ select.className = 'pc-pane-select';
25445
+ for (const opt of optionsList) {
25446
+ const option = document.createElement('option');
25447
+ option.value = opt.value;
25448
+ option.textContent = opt.label;
25449
+ if (opt.value === selectedValue) {
25450
+ option.selected = true;
25451
+ }
25452
+ select.appendChild(option);
25453
+ }
25454
+ return select;
25455
+ }
25456
+ /**
25457
+ * Create a color input element.
25458
+ */
25459
+ createColorInput(value) {
25460
+ const input = document.createElement('input');
25461
+ input.type = 'color';
25462
+ input.className = 'pc-pane-color';
25463
+ if (value) {
25464
+ input.value = value;
25465
+ }
25466
+ return input;
25467
+ }
25468
+ /**
25469
+ * Create a checkbox element.
25470
+ */
25471
+ createCheckbox(label, checked) {
25472
+ const wrapper = document.createElement('label');
25473
+ wrapper.className = 'pc-pane-checkbox';
25474
+ const input = document.createElement('input');
25475
+ input.type = 'checkbox';
25476
+ if (checked) {
25477
+ input.checked = true;
25478
+ }
25479
+ const span = document.createElement('span');
25480
+ span.textContent = label;
25481
+ wrapper.appendChild(input);
25482
+ wrapper.appendChild(span);
25483
+ return wrapper;
25484
+ }
25485
+ /**
25486
+ * Create a button element.
25487
+ */
25488
+ createButton(label, options) {
25489
+ const button = document.createElement('button');
25490
+ button.type = 'button';
25491
+ button.className = 'pc-pane-button';
25492
+ if (options?.variant) {
25493
+ button.classList.add(`pc-pane-button--${options.variant}`);
25494
+ }
25495
+ button.textContent = label;
25496
+ return button;
25497
+ }
25498
+ /**
25499
+ * Create a button group container.
25500
+ */
25501
+ createButtonGroup() {
25502
+ const group = document.createElement('div');
25503
+ group.className = 'pc-pane-button-group';
25504
+ return group;
25505
+ }
25506
+ /**
25507
+ * Create a section divider with optional label.
25508
+ */
25509
+ createSection(label) {
25510
+ const section = document.createElement('div');
25511
+ section.className = 'pc-pane-section';
25512
+ if (label) {
25513
+ const labelEl = document.createElement('div');
25514
+ labelEl.className = 'pc-pane-section-label';
25515
+ labelEl.textContent = label;
25516
+ section.appendChild(labelEl);
25517
+ }
25518
+ return section;
25519
+ }
25520
+ /**
25521
+ * Create a row container for inline elements.
25522
+ */
25523
+ createRow() {
25524
+ const row = document.createElement('div');
25525
+ row.className = 'pc-pane-row';
25526
+ return row;
25527
+ }
25528
+ /**
25529
+ * Create a hint/info text element.
25530
+ */
25531
+ createHint(text) {
25532
+ const hint = document.createElement('div');
25533
+ hint.className = 'pc-pane-hint';
25534
+ hint.textContent = text;
25535
+ return hint;
25536
+ }
25537
+ /**
25538
+ * Add immediate apply listener for text inputs (blur + Enter).
25539
+ */
25540
+ addImmediateApplyListener(element, handler) {
25541
+ const apply = () => {
25542
+ handler(element.value);
25543
+ };
25544
+ // Selects and color inputs: apply on change
25545
+ if (element instanceof HTMLSelectElement ||
25546
+ (element instanceof HTMLInputElement && element.type === 'color')) {
25547
+ element.addEventListener('change', apply);
25548
+ this.eventCleanup.push(() => element.removeEventListener('change', apply));
25549
+ }
25550
+ else {
25551
+ // Text/number inputs: apply on blur or Enter
25552
+ element.addEventListener('blur', apply);
25553
+ const keyHandler = (e) => {
25554
+ if (e.key === 'Enter') {
25555
+ e.preventDefault();
25556
+ apply();
25557
+ }
25558
+ };
25559
+ element.addEventListener('keydown', keyHandler);
25560
+ this.eventCleanup.push(() => {
25561
+ element.removeEventListener('blur', apply);
25562
+ element.removeEventListener('keydown', keyHandler);
25563
+ });
25564
+ }
25565
+ }
25566
+ /**
25567
+ * Add immediate apply listener for checkbox inputs.
25568
+ */
25569
+ addCheckboxListener(element, handler) {
25570
+ const apply = () => handler(element.checked);
25571
+ element.addEventListener('change', apply);
25572
+ this.eventCleanup.push(() => element.removeEventListener('change', apply));
25573
+ }
25574
+ /**
25575
+ * Add button click handler with focus steal prevention.
25576
+ */
25577
+ addButtonListener(button, handler) {
25578
+ // Prevent focus steal on mousedown
25579
+ const preventFocus = (e) => {
25580
+ e.preventDefault();
25581
+ this.saveEditorContext();
25582
+ };
25583
+ button.addEventListener('mousedown', preventFocus);
25584
+ button.addEventListener('click', handler);
25585
+ this.eventCleanup.push(() => {
25586
+ button.removeEventListener('mousedown', preventFocus);
25587
+ button.removeEventListener('click', handler);
25588
+ });
25589
+ }
25590
+ /**
25591
+ * Save editor context before UI elements steal focus.
25592
+ */
25593
+ saveEditorContext() {
25594
+ if (this.editor) {
25595
+ this.editor.saveEditingContext();
25596
+ }
25597
+ }
25598
+ /**
25599
+ * Final createElement that wraps content in pane structure.
25600
+ * Content-only, no title bar.
25601
+ */
25602
+ createElement() {
25603
+ const wrapper = document.createElement('div');
25604
+ wrapper.className = 'pc-pane';
25605
+ if (this.className) {
25606
+ wrapper.classList.add(this.className);
25607
+ }
25608
+ wrapper.setAttribute('data-pane-id', this.id);
25609
+ const content = this.createContent();
25610
+ wrapper.appendChild(content);
25611
+ return wrapper;
25612
+ }
25613
+ }
25614
+
25615
+ /**
25616
+ * DocumentInfoPane - Read-only document information display.
25617
+ *
25618
+ * Shows:
25619
+ * - Page count
25620
+ * - Page size
25621
+ * - Page orientation
25622
+ */
25623
+ class DocumentInfoPane extends BasePane {
25624
+ constructor(id = 'document-info') {
25625
+ super(id, { className: 'pc-pane-document-info' });
25626
+ this.pageCountEl = null;
25627
+ this.pageSizeEl = null;
25628
+ this.pageOrientationEl = null;
25629
+ }
25630
+ attach(options) {
25631
+ super.attach(options);
25632
+ // Subscribe to document changes
25633
+ if (this.editor) {
25634
+ const updateHandler = () => this.update();
25635
+ this.editor.on('document-changed', updateHandler);
25636
+ this.editor.on('page-added', updateHandler);
25637
+ this.editor.on('page-removed', updateHandler);
25638
+ this.eventCleanup.push(() => {
25639
+ this.editor?.off('document-changed', updateHandler);
25640
+ this.editor?.off('page-added', updateHandler);
25641
+ this.editor?.off('page-removed', updateHandler);
25642
+ });
25643
+ // Initial update
25644
+ this.update();
25645
+ }
25646
+ }
25647
+ createContent() {
25648
+ const container = document.createElement('div');
25649
+ container.className = 'pc-pane-info-list';
25650
+ // Page count
25651
+ const countRow = this.createInfoRow('Pages', '0');
25652
+ this.pageCountEl = countRow.querySelector('.pc-pane-info-value');
25653
+ container.appendChild(countRow);
25654
+ // Page size
25655
+ const sizeRow = this.createInfoRow('Size', '-');
25656
+ this.pageSizeEl = sizeRow.querySelector('.pc-pane-info-value');
25657
+ container.appendChild(sizeRow);
25658
+ // Page orientation
25659
+ const orientationRow = this.createInfoRow('Orientation', '-');
25660
+ this.pageOrientationEl = orientationRow.querySelector('.pc-pane-info-value');
25661
+ container.appendChild(orientationRow);
25662
+ return container;
25663
+ }
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;
25676
+ }
25677
+ /**
25678
+ * Update the displayed information from the editor.
25679
+ */
25680
+ update() {
25681
+ if (!this.editor)
25682
+ return;
25683
+ const doc = this.editor.getDocument();
25684
+ if (this.pageCountEl) {
25685
+ this.pageCountEl.textContent = doc.pages.length.toString();
25686
+ }
25687
+ if (this.pageSizeEl && doc.settings) {
25688
+ this.pageSizeEl.textContent = doc.settings.pageSize;
25689
+ }
25690
+ if (this.pageOrientationEl && doc.settings) {
25691
+ const orientation = doc.settings.pageOrientation;
25692
+ this.pageOrientationEl.textContent =
25693
+ orientation.charAt(0).toUpperCase() + orientation.slice(1);
25694
+ }
25695
+ }
25696
+ }
25697
+
25698
+ /**
25699
+ * ViewSettingsPane - Toggle buttons for view options.
25700
+ *
25701
+ * Toggles:
25702
+ * - Rulers (requires external callback since rulers are optional controls)
25703
+ * - Control characters
25704
+ * - Margin lines
25705
+ * - Grid
25706
+ */
25707
+ class ViewSettingsPane extends BasePane {
25708
+ constructor(id = 'view-settings', options = {}) {
25709
+ super(id, { className: 'pc-pane-view-settings', ...options });
25710
+ this.rulersBtn = null;
25711
+ this.controlCharsBtn = null;
25712
+ this.marginLinesBtn = null;
25713
+ this.gridBtn = null;
25714
+ this.onToggleRulers = options.onToggleRulers;
25715
+ this.rulersVisible = options.rulersVisible ?? true;
25716
+ }
25717
+ attach(options) {
25718
+ super.attach(options);
25719
+ // Subscribe to editor events
25720
+ if (this.editor) {
25721
+ const updateHandler = () => this.updateButtonStates();
25722
+ this.editor.on('grid-changed', updateHandler);
25723
+ this.editor.on('margin-lines-changed', updateHandler);
25724
+ this.editor.on('control-characters-changed', updateHandler);
25725
+ this.eventCleanup.push(() => {
25726
+ this.editor?.off('grid-changed', updateHandler);
25727
+ this.editor?.off('margin-lines-changed', updateHandler);
25728
+ this.editor?.off('control-characters-changed', updateHandler);
25729
+ });
25730
+ // Initial state
25731
+ this.updateButtonStates();
25732
+ }
25733
+ }
25734
+ createContent() {
25735
+ const container = document.createElement('div');
25736
+ container.className = 'pc-pane-button-group pc-pane-view-toggles';
25737
+ // Rulers toggle (only if callback provided)
25738
+ if (this.onToggleRulers) {
25739
+ this.rulersBtn = this.createToggleButton('Rulers', this.rulersVisible);
25740
+ this.addButtonListener(this.rulersBtn, () => this.toggleRulers());
25741
+ container.appendChild(this.rulersBtn);
25742
+ }
25743
+ // Control characters toggle
25744
+ this.controlCharsBtn = this.createToggleButton('Control Chars', false);
25745
+ this.addButtonListener(this.controlCharsBtn, () => this.toggleControlChars());
25746
+ container.appendChild(this.controlCharsBtn);
25747
+ // Margin lines toggle
25748
+ this.marginLinesBtn = this.createToggleButton('Margin Lines', true);
25749
+ this.addButtonListener(this.marginLinesBtn, () => this.toggleMarginLines());
25750
+ container.appendChild(this.marginLinesBtn);
25751
+ // Grid toggle
25752
+ this.gridBtn = this.createToggleButton('Grid', true);
25753
+ this.addButtonListener(this.gridBtn, () => this.toggleGrid());
25754
+ container.appendChild(this.gridBtn);
25755
+ return container;
25756
+ }
25757
+ createToggleButton(label, active) {
25758
+ const button = document.createElement('button');
25759
+ button.type = 'button';
25760
+ button.className = 'pc-pane-toggle';
25761
+ if (active) {
25762
+ button.classList.add('pc-pane-toggle--active');
25763
+ }
25764
+ button.textContent = label;
25765
+ button.title = `Toggle ${label}`;
25766
+ return button;
25767
+ }
25768
+ toggleRulers() {
25769
+ if (this.onToggleRulers) {
25770
+ this.onToggleRulers();
25771
+ this.rulersVisible = !this.rulersVisible;
25772
+ this.rulersBtn?.classList.toggle('pc-pane-toggle--active', this.rulersVisible);
25773
+ }
25774
+ }
25775
+ toggleControlChars() {
25776
+ if (!this.editor)
25777
+ return;
25778
+ const current = this.editor.getShowControlCharacters();
25779
+ this.editor.setShowControlCharacters(!current);
25780
+ }
25781
+ toggleMarginLines() {
25782
+ if (!this.editor)
25783
+ return;
25784
+ const current = this.editor.getShowMarginLines();
25785
+ this.editor.setShowMarginLines(!current);
25786
+ }
25787
+ toggleGrid() {
25788
+ if (!this.editor)
25789
+ return;
25790
+ const current = this.editor.getShowGrid();
25791
+ this.editor.setShowGrid(!current);
25792
+ }
25793
+ updateButtonStates() {
25794
+ if (!this.editor)
25795
+ return;
25796
+ if (this.controlCharsBtn) {
25797
+ this.controlCharsBtn.classList.toggle('pc-pane-toggle--active', this.editor.getShowControlCharacters());
25798
+ }
25799
+ if (this.marginLinesBtn) {
25800
+ this.marginLinesBtn.classList.toggle('pc-pane-toggle--active', this.editor.getShowMarginLines());
25801
+ }
25802
+ if (this.gridBtn) {
25803
+ this.gridBtn.classList.toggle('pc-pane-toggle--active', this.editor.getShowGrid());
25804
+ }
25805
+ }
25806
+ /**
25807
+ * Update ruler button state externally (since rulers are external controls).
25808
+ */
25809
+ setRulersVisible(visible) {
25810
+ this.rulersVisible = visible;
25811
+ this.rulersBtn?.classList.toggle('pc-pane-toggle--active', visible);
25812
+ }
25813
+ /**
25814
+ * Update the pane from current editor state.
25815
+ */
25816
+ update() {
25817
+ this.updateButtonStates();
25818
+ }
25819
+ }
25820
+
25821
+ /**
25822
+ * DocumentSettingsPane - Edit margins, page size, and orientation.
25823
+ *
25824
+ * Uses the PCEditor public API:
25825
+ * - editor.getDocumentSettings()
25826
+ * - editor.updateDocumentSettings()
25827
+ */
25828
+ class DocumentSettingsPane extends BasePane {
25829
+ constructor(id = 'document-settings') {
25830
+ super(id, { className: 'pc-pane-document-settings' });
25831
+ this.marginTopInput = null;
25832
+ this.marginRightInput = null;
25833
+ this.marginBottomInput = null;
25834
+ this.marginLeftInput = null;
25835
+ this.pageSizeSelect = null;
25836
+ this.orientationSelect = null;
25837
+ }
25838
+ attach(options) {
25839
+ super.attach(options);
25840
+ // Load current settings
25841
+ if (this.editor) {
25842
+ this.loadSettings();
25843
+ // Subscribe to document changes
25844
+ const updateHandler = () => this.loadSettings();
25845
+ this.editor.on('document-changed', updateHandler);
25846
+ this.eventCleanup.push(() => {
25847
+ this.editor?.off('document-changed', updateHandler);
25848
+ });
25849
+ }
25850
+ }
25851
+ createContent() {
25852
+ const container = document.createElement('div');
25853
+ // Margins section
25854
+ const marginsSection = this.createSection('Margins (mm)');
25855
+ const marginsGrid = document.createElement('div');
25856
+ marginsGrid.className = 'pc-pane-margins-grid';
25857
+ this.marginTopInput = this.createNumberInput({ min: 5, max: 50, step: 0.5, value: 20 });
25858
+ this.marginRightInput = this.createNumberInput({ min: 5, max: 50, step: 0.5, value: 20 });
25859
+ this.marginBottomInput = this.createNumberInput({ min: 5, max: 50, step: 0.5, value: 20 });
25860
+ 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 }));
25865
+ 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
+ container.appendChild(marginsSection);
25871
+ // Page size section
25872
+ const pageSizeSection = this.createSection();
25873
+ this.pageSizeSelect = this.createSelect([
25874
+ { value: 'A4', label: 'A4' },
25875
+ { value: 'Letter', label: 'Letter' },
25876
+ { value: 'Legal', label: 'Legal' },
25877
+ { value: 'A3', label: 'A3' }
25878
+ ], 'A4');
25879
+ 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();
25884
+ this.orientationSelect = this.createSelect([
25885
+ { value: 'portrait', label: 'Portrait' },
25886
+ { value: 'landscape', label: 'Landscape' }
25887
+ ], 'portrait');
25888
+ this.addImmediateApplyListener(this.orientationSelect, () => this.applyPageSettings());
25889
+ orientationSection.appendChild(this.createFormGroup('Orientation', this.orientationSelect));
25890
+ container.appendChild(orientationSection);
25891
+ return container;
25892
+ }
25893
+ loadSettings() {
25894
+ if (!this.editor)
25895
+ return;
25896
+ try {
25897
+ const settings = this.editor.getDocumentSettings();
25898
+ if (this.marginTopInput) {
25899
+ this.marginTopInput.value = settings.margins.top.toString();
25900
+ }
25901
+ if (this.marginRightInput) {
25902
+ this.marginRightInput.value = settings.margins.right.toString();
25903
+ }
25904
+ if (this.marginBottomInput) {
25905
+ this.marginBottomInput.value = settings.margins.bottom.toString();
25906
+ }
25907
+ if (this.marginLeftInput) {
25908
+ this.marginLeftInput.value = settings.margins.left.toString();
25909
+ }
25910
+ if (this.pageSizeSelect) {
25911
+ this.pageSizeSelect.value = settings.pageSize;
25912
+ }
25913
+ if (this.orientationSelect) {
25914
+ this.orientationSelect.value = settings.pageOrientation;
25915
+ }
25916
+ }
25917
+ catch (error) {
25918
+ console.error('Failed to load document settings:', error);
25919
+ }
25920
+ }
25921
+ applyMargins() {
25922
+ if (!this.editor)
25923
+ return;
25924
+ const margins = {
25925
+ top: parseFloat(this.marginTopInput?.value || '20'),
25926
+ right: parseFloat(this.marginRightInput?.value || '20'),
25927
+ bottom: parseFloat(this.marginBottomInput?.value || '20'),
25928
+ left: parseFloat(this.marginLeftInput?.value || '20')
25929
+ };
25930
+ try {
25931
+ this.editor.updateDocumentSettings({ margins });
25932
+ }
25933
+ catch (error) {
25934
+ console.error('Failed to update margins:', error);
25935
+ }
25936
+ }
25937
+ applyPageSettings() {
25938
+ if (!this.editor)
25939
+ return;
25940
+ const settings = {};
25941
+ if (this.pageSizeSelect) {
25942
+ settings.pageSize = this.pageSizeSelect.value;
25943
+ }
25944
+ if (this.orientationSelect) {
25945
+ settings.pageOrientation = this.orientationSelect.value;
25946
+ }
25947
+ try {
25948
+ this.editor.updateDocumentSettings(settings);
25949
+ }
25950
+ catch (error) {
25951
+ console.error('Failed to update page settings:', error);
25952
+ }
25953
+ }
25954
+ /**
25955
+ * Update the pane from current editor state.
25956
+ */
25957
+ update() {
25958
+ this.loadSettings();
25959
+ }
25960
+ }
25961
+
25962
+ /**
25963
+ * MergeDataPane - JSON data input for mail merge/substitution.
25964
+ *
25965
+ * Uses the PCEditor public API:
25966
+ * - editor.applyMergeData()
25967
+ */
25968
+ class MergeDataPane extends BasePane {
25969
+ constructor(id = 'merge-data', options = {}) {
25970
+ super(id, { className: 'pc-pane-merge-data', ...options });
25971
+ this.textarea = null;
25972
+ this.errorHint = null;
25973
+ this.initialData = options.initialData;
25974
+ this.placeholder = options.placeholder || '{"customerName": "John Doe", "orderNumber": "12345"}';
25975
+ this.rows = options.rows ?? 10;
25976
+ this.onApply = options.onApply;
25977
+ }
25978
+ createContent() {
25979
+ const container = document.createElement('div');
25980
+ // Textarea for JSON
25981
+ const textareaGroup = this.createFormGroup('JSON Data', this.createTextarea());
25982
+ container.appendChild(textareaGroup);
25983
+ // Error hint (hidden by default)
25984
+ this.errorHint = this.createHint('');
25985
+ this.errorHint.style.display = 'none';
25986
+ this.errorHint.style.color = '#dc3545';
25987
+ container.appendChild(this.errorHint);
25988
+ // Apply button
25989
+ const applyBtn = this.createButton('Apply Merge Data', { variant: 'primary' });
25990
+ this.addButtonListener(applyBtn, () => this.applyMergeData());
25991
+ container.appendChild(applyBtn);
25992
+ return container;
25993
+ }
25994
+ createTextarea() {
25995
+ this.textarea = document.createElement('textarea');
25996
+ this.textarea.className = 'pc-pane-textarea pc-pane-merge-data-input';
25997
+ this.textarea.rows = this.rows;
25998
+ this.textarea.placeholder = this.placeholder;
25999
+ this.textarea.spellcheck = false;
26000
+ if (this.initialData) {
26001
+ this.textarea.value = JSON.stringify(this.initialData, null, 2);
26002
+ }
26003
+ // Clear error on input
26004
+ this.textarea.addEventListener('input', () => {
26005
+ if (this.errorHint) {
26006
+ this.errorHint.style.display = 'none';
26007
+ }
26008
+ });
26009
+ return this.textarea;
26010
+ }
26011
+ applyMergeData() {
26012
+ if (!this.editor || !this.textarea)
26013
+ return;
26014
+ try {
26015
+ const mergeData = JSON.parse(this.textarea.value);
26016
+ this.editor.applyMergeData(mergeData);
26017
+ if (this.errorHint) {
26018
+ this.errorHint.style.display = 'none';
26019
+ }
26020
+ this.onApply?.(true);
26021
+ }
26022
+ catch (error) {
26023
+ const err = error instanceof Error ? error : new Error(String(error));
26024
+ if (this.errorHint) {
26025
+ if (error instanceof SyntaxError) {
26026
+ this.errorHint.textContent = 'Invalid JSON syntax';
26027
+ }
26028
+ else {
26029
+ this.errorHint.textContent = err.message;
26030
+ }
26031
+ this.errorHint.style.display = 'block';
26032
+ }
26033
+ this.onApply?.(false, err);
26034
+ }
26035
+ }
26036
+ /**
26037
+ * Get the current JSON data from the textarea.
26038
+ * Returns null if the JSON is invalid.
26039
+ */
26040
+ getData() {
26041
+ if (!this.textarea)
26042
+ return null;
26043
+ try {
26044
+ return JSON.parse(this.textarea.value);
26045
+ }
26046
+ catch {
26047
+ return null;
26048
+ }
26049
+ }
26050
+ /**
26051
+ * Set the JSON data in the textarea.
26052
+ */
26053
+ setData(data) {
26054
+ if (this.textarea) {
26055
+ this.textarea.value = JSON.stringify(data, null, 2);
26056
+ if (this.errorHint) {
26057
+ this.errorHint.style.display = 'none';
26058
+ }
26059
+ }
26060
+ }
26061
+ /**
26062
+ * Update the pane (no-op for MergeDataPane as it doesn't track editor state).
26063
+ */
26064
+ update() {
26065
+ // MergeDataPane doesn't need to update from editor state
26066
+ }
26067
+ }
26068
+
26069
+ /**
26070
+ * FormattingPane - Text formatting controls.
26071
+ *
26072
+ * Controls:
26073
+ * - Bold/Italic toggles
26074
+ * - Alignment buttons (left, center, right, justify)
26075
+ * - List buttons (bullet, numbered, indent, outdent)
26076
+ * - Font family/size dropdowns
26077
+ * - Text color and highlight color pickers
26078
+ *
26079
+ * Uses the PCEditor public API:
26080
+ * - editor.getUnifiedFormattingAtCursor()
26081
+ * - editor.applyFormattingWithFallback()
26082
+ * - editor.setPendingFormatting()
26083
+ * - editor.getSavedOrCurrentSelection()
26084
+ * - editor.getUnifiedAlignmentAtCursor()
26085
+ * - editor.setUnifiedAlignment()
26086
+ * - editor.toggleBulletList()
26087
+ * - editor.toggleNumberedList()
26088
+ * - editor.indentParagraph()
26089
+ * - editor.outdentParagraph()
26090
+ * - editor.getListFormatting()
26091
+ */
26092
+ const DEFAULT_FONT_FAMILIES = [
26093
+ 'Arial',
26094
+ 'Times New Roman',
26095
+ 'Georgia',
26096
+ 'Verdana',
26097
+ 'Courier New'
26098
+ ];
26099
+ const DEFAULT_FONT_SIZES = [10, 12, 14, 16, 18, 20, 24, 28, 32, 36];
26100
+ class FormattingPane extends BasePane {
26101
+ constructor(id = 'formatting', options = {}) {
26102
+ super(id, { className: 'pc-pane-formatting', ...options });
26103
+ // Style toggles
26104
+ this.boldBtn = null;
26105
+ this.italicBtn = null;
26106
+ // Alignment buttons
26107
+ this.alignLeftBtn = null;
26108
+ this.alignCenterBtn = null;
26109
+ this.alignRightBtn = null;
26110
+ this.alignJustifyBtn = null;
26111
+ // List buttons
26112
+ this.bulletListBtn = null;
26113
+ this.numberedListBtn = null;
26114
+ this.indentBtn = null;
26115
+ this.outdentBtn = null;
26116
+ // Font controls
26117
+ this.fontFamilySelect = null;
26118
+ this.fontSizeSelect = null;
26119
+ this.colorInput = null;
26120
+ this.highlightInput = null;
26121
+ this.fontFamilies = options.fontFamilies ?? DEFAULT_FONT_FAMILIES;
26122
+ this.fontSizes = options.fontSizes ?? DEFAULT_FONT_SIZES;
26123
+ }
26124
+ attach(options) {
26125
+ super.attach(options);
26126
+ if (this.editor) {
26127
+ // Update on cursor/selection changes
26128
+ const updateHandler = () => this.updateFromEditor();
26129
+ this.editor.on('cursor-changed', updateHandler);
26130
+ this.editor.on('selection-changed', updateHandler);
26131
+ this.editor.on('text-changed', updateHandler);
26132
+ this.editor.on('formatting-changed', updateHandler);
26133
+ this.eventCleanup.push(() => {
26134
+ this.editor?.off('cursor-changed', updateHandler);
26135
+ this.editor?.off('selection-changed', updateHandler);
26136
+ this.editor?.off('text-changed', updateHandler);
26137
+ this.editor?.off('formatting-changed', updateHandler);
26138
+ });
26139
+ // Initial update
26140
+ this.updateFromEditor();
26141
+ }
26142
+ }
26143
+ createContent() {
26144
+ const container = document.createElement('div');
26145
+ // Style section (Bold, Italic)
26146
+ const styleSection = this.createSection('Style');
26147
+ const styleGroup = this.createButtonGroup();
26148
+ this.boldBtn = this.createButton('B');
26149
+ this.boldBtn.title = 'Bold';
26150
+ this.boldBtn.style.fontWeight = 'bold';
26151
+ this.addButtonListener(this.boldBtn, () => this.toggleBold());
26152
+ this.italicBtn = this.createButton('I');
26153
+ this.italicBtn.title = 'Italic';
26154
+ this.italicBtn.style.fontStyle = 'italic';
26155
+ this.addButtonListener(this.italicBtn, () => this.toggleItalic());
26156
+ styleGroup.appendChild(this.boldBtn);
26157
+ styleGroup.appendChild(this.italicBtn);
26158
+ styleSection.appendChild(styleGroup);
26159
+ container.appendChild(styleSection);
26160
+ // Alignment section
26161
+ const alignSection = this.createSection('Alignment');
26162
+ const alignGroup = this.createButtonGroup();
26163
+ this.alignLeftBtn = this.createButton('');
26164
+ this.alignLeftBtn.title = 'Align Left';
26165
+ this.alignLeftBtn.classList.add('pc-pane-button--icon', 'pc-pane-button--align-left');
26166
+ this.addButtonListener(this.alignLeftBtn, () => this.setAlignment('left'));
26167
+ this.alignCenterBtn = this.createButton('');
26168
+ this.alignCenterBtn.title = 'Center';
26169
+ this.alignCenterBtn.classList.add('pc-pane-button--icon', 'pc-pane-button--align-center');
26170
+ this.addButtonListener(this.alignCenterBtn, () => this.setAlignment('center'));
26171
+ this.alignRightBtn = this.createButton('');
26172
+ this.alignRightBtn.title = 'Align Right';
26173
+ this.alignRightBtn.classList.add('pc-pane-button--icon', 'pc-pane-button--align-right');
26174
+ this.addButtonListener(this.alignRightBtn, () => this.setAlignment('right'));
26175
+ this.alignJustifyBtn = this.createButton('');
26176
+ this.alignJustifyBtn.title = 'Justify';
26177
+ this.alignJustifyBtn.classList.add('pc-pane-button--icon', 'pc-pane-button--align-justify');
26178
+ this.addButtonListener(this.alignJustifyBtn, () => this.setAlignment('justify'));
26179
+ alignGroup.appendChild(this.alignLeftBtn);
26180
+ alignGroup.appendChild(this.alignCenterBtn);
26181
+ alignGroup.appendChild(this.alignRightBtn);
26182
+ alignGroup.appendChild(this.alignJustifyBtn);
26183
+ alignSection.appendChild(alignGroup);
26184
+ container.appendChild(alignSection);
26185
+ // Lists section
26186
+ const listsSection = this.createSection('Lists');
26187
+ const listsGroup = this.createButtonGroup();
26188
+ this.bulletListBtn = this.createButton('\u2022'); // •
26189
+ this.bulletListBtn.title = 'Bullet List';
26190
+ this.addButtonListener(this.bulletListBtn, () => this.toggleBulletList());
26191
+ this.numberedListBtn = this.createButton('1.');
26192
+ this.numberedListBtn.title = 'Numbered List';
26193
+ this.addButtonListener(this.numberedListBtn, () => this.toggleNumberedList());
26194
+ this.indentBtn = this.createButton('\u2192'); // →
26195
+ this.indentBtn.title = 'Increase Indent';
26196
+ this.addButtonListener(this.indentBtn, () => this.indent());
26197
+ this.outdentBtn = this.createButton('\u2190'); // ←
26198
+ this.outdentBtn.title = 'Decrease Indent';
26199
+ this.addButtonListener(this.outdentBtn, () => this.outdent());
26200
+ listsGroup.appendChild(this.bulletListBtn);
26201
+ listsGroup.appendChild(this.numberedListBtn);
26202
+ listsGroup.appendChild(this.indentBtn);
26203
+ listsGroup.appendChild(this.outdentBtn);
26204
+ listsSection.appendChild(listsGroup);
26205
+ container.appendChild(listsSection);
26206
+ // Font section
26207
+ const fontSection = this.createSection('Font');
26208
+ this.fontFamilySelect = this.createSelect(this.fontFamilies.map(f => ({ value: f, label: f })), 'Arial');
26209
+ this.addImmediateApplyListener(this.fontFamilySelect, () => this.applyFontFamily());
26210
+ fontSection.appendChild(this.createFormGroup('Family', this.fontFamilySelect));
26211
+ this.fontSizeSelect = this.createSelect(this.fontSizes.map(s => ({ value: s.toString(), label: s.toString() })), '14');
26212
+ this.addImmediateApplyListener(this.fontSizeSelect, () => this.applyFontSize());
26213
+ fontSection.appendChild(this.createFormGroup('Size', this.fontSizeSelect));
26214
+ container.appendChild(fontSection);
26215
+ // Color section
26216
+ const colorSection = this.createSection('Color');
26217
+ const colorRow = this.createRow();
26218
+ const colorGroup = document.createElement('div');
26219
+ this.colorInput = this.createColorInput('#000000');
26220
+ this.addImmediateApplyListener(this.colorInput, () => this.applyTextColor());
26221
+ colorGroup.appendChild(this.createFormGroup('Text', this.colorInput));
26222
+ colorRow.appendChild(colorGroup);
26223
+ const highlightGroup = document.createElement('div');
26224
+ this.highlightInput = this.createColorInput('#ffff00');
26225
+ this.addImmediateApplyListener(this.highlightInput, () => this.applyHighlight());
26226
+ const highlightForm = this.createFormGroup('Highlight', this.highlightInput);
26227
+ const clearHighlightBtn = this.createButton('Clear');
26228
+ clearHighlightBtn.className = 'pc-pane-button';
26229
+ clearHighlightBtn.style.marginLeft = '4px';
26230
+ this.addButtonListener(clearHighlightBtn, () => this.clearHighlight());
26231
+ highlightForm.appendChild(clearHighlightBtn);
26232
+ highlightGroup.appendChild(highlightForm);
26233
+ colorRow.appendChild(highlightGroup);
26234
+ colorSection.appendChild(colorRow);
26235
+ container.appendChild(colorSection);
26236
+ return container;
26237
+ }
26238
+ updateFromEditor() {
26239
+ if (!this.editor)
26240
+ return;
26241
+ // Get formatting at cursor
26242
+ const formatting = this.editor.getUnifiedFormattingAtCursor();
26243
+ if (formatting) {
26244
+ // Update bold button
26245
+ this.boldBtn?.classList.toggle('pc-pane-button--active', formatting.fontWeight === 'bold');
26246
+ // Update italic button
26247
+ this.italicBtn?.classList.toggle('pc-pane-button--active', formatting.fontStyle === 'italic');
26248
+ // Update font family
26249
+ if (this.fontFamilySelect && formatting.fontFamily) {
26250
+ this.fontFamilySelect.value = formatting.fontFamily;
26251
+ }
26252
+ // Update font size
26253
+ if (this.fontSizeSelect && formatting.fontSize) {
26254
+ this.fontSizeSelect.value = formatting.fontSize.toString();
26255
+ }
26256
+ // Update color
26257
+ if (this.colorInput && formatting.color) {
26258
+ this.colorInput.value = formatting.color;
26259
+ }
26260
+ // Update highlight
26261
+ if (this.highlightInput && formatting.backgroundColor) {
26262
+ this.highlightInput.value = formatting.backgroundColor;
26263
+ }
26264
+ }
26265
+ // Update alignment buttons
26266
+ const alignment = this.editor.getUnifiedAlignmentAtCursor();
26267
+ this.updateAlignmentButtons(alignment);
26268
+ // Update list buttons
26269
+ this.updateListButtons();
26270
+ }
26271
+ updateAlignmentButtons(alignment) {
26272
+ const buttons = [
26273
+ { btn: this.alignLeftBtn, align: 'left' },
26274
+ { btn: this.alignCenterBtn, align: 'center' },
26275
+ { btn: this.alignRightBtn, align: 'right' },
26276
+ { btn: this.alignJustifyBtn, align: 'justify' }
26277
+ ];
26278
+ for (const { btn, align } of buttons) {
26279
+ btn?.classList.toggle('pc-pane-button--active', align === alignment);
26280
+ }
26281
+ }
26282
+ updateListButtons() {
26283
+ if (!this.editor)
26284
+ return;
26285
+ try {
26286
+ const listFormatting = this.editor.getListFormatting();
26287
+ if (listFormatting) {
26288
+ this.bulletListBtn?.classList.toggle('pc-pane-button--active', listFormatting.listType === 'bullet');
26289
+ this.numberedListBtn?.classList.toggle('pc-pane-button--active', listFormatting.listType === 'number');
26290
+ }
26291
+ }
26292
+ catch {
26293
+ // No text editing active
26294
+ }
26295
+ }
26296
+ getSelection() {
26297
+ if (!this.editor)
26298
+ return null;
26299
+ return this.editor.getSavedOrCurrentSelection();
26300
+ }
26301
+ applyFormatting(formatting) {
26302
+ if (!this.editor)
26303
+ return;
26304
+ const selection = this.getSelection();
26305
+ try {
26306
+ if (selection) {
26307
+ this.editor.applyFormattingWithFallback(selection.start, selection.end, formatting);
26308
+ }
26309
+ else {
26310
+ this.editor.setPendingFormatting(formatting);
26311
+ }
26312
+ this.editor.clearSavedEditingContext();
26313
+ this.updateFromEditor();
26314
+ this.editor.enableTextInput();
26315
+ }
26316
+ catch (error) {
26317
+ console.error('Formatting error:', error);
26318
+ }
26319
+ }
26320
+ toggleBold() {
26321
+ const isActive = this.boldBtn?.classList.contains('pc-pane-button--active');
26322
+ this.applyFormatting({ fontWeight: isActive ? 'normal' : 'bold' });
26323
+ }
26324
+ toggleItalic() {
26325
+ const isActive = this.italicBtn?.classList.contains('pc-pane-button--active');
26326
+ this.applyFormatting({ fontStyle: isActive ? 'normal' : 'italic' });
26327
+ }
26328
+ applyFontFamily() {
26329
+ if (this.fontFamilySelect) {
26330
+ this.applyFormatting({ fontFamily: this.fontFamilySelect.value });
26331
+ }
26332
+ }
26333
+ applyFontSize() {
26334
+ if (this.fontSizeSelect) {
26335
+ this.applyFormatting({ fontSize: parseInt(this.fontSizeSelect.value, 10) });
26336
+ }
26337
+ }
26338
+ applyTextColor() {
26339
+ if (this.colorInput) {
26340
+ this.applyFormatting({ color: this.colorInput.value });
26341
+ }
26342
+ }
26343
+ applyHighlight() {
26344
+ if (this.highlightInput) {
26345
+ this.applyFormatting({ backgroundColor: this.highlightInput.value });
26346
+ }
26347
+ }
26348
+ clearHighlight() {
26349
+ this.applyFormatting({ backgroundColor: undefined });
26350
+ }
26351
+ setAlignment(alignment) {
26352
+ if (!this.editor)
26353
+ return;
26354
+ try {
26355
+ this.editor.setUnifiedAlignment(alignment);
26356
+ this.updateAlignmentButtons(alignment);
26357
+ }
26358
+ catch (error) {
26359
+ console.error('Alignment error:', error);
26360
+ }
26361
+ }
26362
+ toggleBulletList() {
26363
+ if (!this.editor)
26364
+ return;
26365
+ try {
26366
+ this.editor.toggleBulletList();
26367
+ this.updateListButtons();
26368
+ }
26369
+ catch (error) {
26370
+ console.error('Bullet list error:', error);
26371
+ }
26372
+ }
26373
+ toggleNumberedList() {
26374
+ if (!this.editor)
26375
+ return;
26376
+ try {
26377
+ this.editor.toggleNumberedList();
26378
+ this.updateListButtons();
26379
+ }
26380
+ catch (error) {
26381
+ console.error('Numbered list error:', error);
26382
+ }
26383
+ }
26384
+ indent() {
26385
+ if (!this.editor)
26386
+ return;
26387
+ try {
26388
+ this.editor.indentParagraph();
26389
+ this.updateListButtons();
26390
+ }
26391
+ catch (error) {
26392
+ console.error('Indent error:', error);
26393
+ }
26394
+ }
26395
+ outdent() {
26396
+ if (!this.editor)
26397
+ return;
26398
+ try {
26399
+ this.editor.outdentParagraph();
26400
+ this.updateListButtons();
26401
+ }
26402
+ catch (error) {
26403
+ console.error('Outdent error:', error);
26404
+ }
26405
+ }
26406
+ /**
26407
+ * Update the pane from current editor state.
26408
+ */
26409
+ update() {
26410
+ this.updateFromEditor();
26411
+ }
26412
+ }
26413
+
26414
+ /**
26415
+ * HyperlinkPane - Edit hyperlink URL and title.
26416
+ *
26417
+ * This pane is shown when a hyperlink is selected/cursor is in a hyperlink.
26418
+ *
26419
+ * Uses the PCEditor public API:
26420
+ * - editor.getHyperlinkAt()
26421
+ * - editor.updateHyperlink()
26422
+ * - editor.removeHyperlink()
26423
+ * - editor.getCursorPosition()
26424
+ */
26425
+ class HyperlinkPane extends BasePane {
26426
+ constructor(id = 'hyperlink', options = {}) {
26427
+ super(id, { className: 'pc-pane-hyperlink', ...options });
26428
+ this.urlInput = null;
26429
+ this.titleInput = null;
26430
+ this.rangeHint = null;
26431
+ this.currentHyperlink = null;
26432
+ this.onApply = options.onApply;
26433
+ this.onRemove = options.onRemove;
26434
+ }
26435
+ attach(options) {
26436
+ super.attach(options);
26437
+ if (this.editor) {
26438
+ // Update on cursor changes
26439
+ const updateHandler = () => this.updateFromCursor();
26440
+ this.editor.on('cursor-changed', updateHandler);
26441
+ this.editor.on('selection-changed', updateHandler);
26442
+ this.eventCleanup.push(() => {
26443
+ this.editor?.off('cursor-changed', updateHandler);
26444
+ this.editor?.off('selection-changed', updateHandler);
26445
+ });
26446
+ // Initial update
26447
+ this.updateFromCursor();
26448
+ }
26449
+ }
26450
+ createContent() {
26451
+ const container = document.createElement('div');
26452
+ // URL input
26453
+ this.urlInput = this.createTextInput({ placeholder: 'https://example.com' });
26454
+ container.appendChild(this.createFormGroup('URL', this.urlInput));
26455
+ // Title input
26456
+ this.titleInput = this.createTextInput({ placeholder: 'Link title (optional)' });
26457
+ container.appendChild(this.createFormGroup('Title', this.titleInput));
26458
+ // Apply button
26459
+ const applyBtn = this.createButton('Apply Changes', { variant: 'primary' });
26460
+ this.addButtonListener(applyBtn, () => this.applyChanges());
26461
+ container.appendChild(applyBtn);
26462
+ // Remove button
26463
+ const removeBtn = this.createButton('Remove Link', { variant: 'danger' });
26464
+ removeBtn.style.marginTop = '0.5rem';
26465
+ this.addButtonListener(removeBtn, () => this.removeHyperlink());
26466
+ container.appendChild(removeBtn);
26467
+ // Range hint
26468
+ this.rangeHint = this.createHint('');
26469
+ container.appendChild(this.rangeHint);
26470
+ return container;
26471
+ }
26472
+ updateFromCursor() {
26473
+ if (!this.editor)
26474
+ return;
26475
+ const cursorPos = this.editor.getCursorPosition();
26476
+ const hyperlink = this.editor.getHyperlinkAt(cursorPos);
26477
+ if (hyperlink) {
26478
+ this.showHyperlink(hyperlink);
26479
+ }
26480
+ else {
26481
+ this.hideHyperlink();
26482
+ }
26483
+ }
26484
+ showHyperlink(hyperlink) {
26485
+ this.currentHyperlink = hyperlink;
26486
+ if (this.urlInput) {
26487
+ this.urlInput.value = hyperlink.url;
26488
+ }
26489
+ if (this.titleInput) {
26490
+ this.titleInput.value = hyperlink.title || '';
26491
+ }
26492
+ if (this.rangeHint) {
26493
+ this.rangeHint.textContent = `Link spans characters ${hyperlink.startIndex} to ${hyperlink.endIndex}`;
26494
+ }
26495
+ // Show the pane
26496
+ this.show();
26497
+ }
26498
+ hideHyperlink() {
26499
+ this.currentHyperlink = null;
26500
+ this.hide();
26501
+ }
26502
+ applyChanges() {
26503
+ if (!this.editor || !this.currentHyperlink)
26504
+ return;
26505
+ try {
26506
+ const url = this.urlInput?.value.trim() || '';
26507
+ const title = this.titleInput?.value.trim() || undefined;
26508
+ if (!url) {
26509
+ this.onApply?.(false, new Error('URL is required'));
26510
+ return;
26511
+ }
26512
+ this.editor.updateHyperlink(this.currentHyperlink.id, { url, title });
26513
+ // Update local reference
26514
+ this.currentHyperlink.url = url;
26515
+ this.currentHyperlink.title = title;
26516
+ this.onApply?.(true);
26517
+ }
26518
+ catch (error) {
26519
+ this.onApply?.(false, error instanceof Error ? error : new Error(String(error)));
26520
+ }
26521
+ }
26522
+ removeHyperlink() {
26523
+ if (!this.editor || !this.currentHyperlink)
26524
+ return;
26525
+ try {
26526
+ this.editor.removeHyperlink(this.currentHyperlink.id);
26527
+ this.hideHyperlink();
26528
+ this.onRemove?.(true);
26529
+ }
26530
+ catch {
26531
+ this.onRemove?.(false);
26532
+ }
26533
+ }
26534
+ /**
26535
+ * Get the currently selected hyperlink.
26536
+ */
26537
+ getCurrentHyperlink() {
26538
+ return this.currentHyperlink;
26539
+ }
26540
+ /**
26541
+ * Check if a hyperlink is currently selected.
26542
+ */
26543
+ hasHyperlink() {
26544
+ return this.currentHyperlink !== null;
26545
+ }
26546
+ /**
26547
+ * Update the pane from current editor state.
26548
+ */
26549
+ update() {
26550
+ this.updateFromCursor();
26551
+ }
26552
+ }
26553
+
26554
+ /**
26555
+ * SubstitutionFieldPane - Edit substitution field properties.
26556
+ *
26557
+ * Shows:
26558
+ * - Field name
26559
+ * - Default value
26560
+ * - Format configuration (value type, number/currency/date formats)
26561
+ *
26562
+ * Uses the PCEditor public API:
26563
+ * - editor.getFieldAt()
26564
+ * - editor.updateField()
26565
+ */
26566
+ class SubstitutionFieldPane extends BasePane {
26567
+ constructor(id = 'substitution-field', options = {}) {
26568
+ super(id, { className: 'pc-pane-substitution-field', ...options });
26569
+ this.fieldNameInput = null;
26570
+ this.fieldDefaultInput = null;
26571
+ this.valueTypeSelect = null;
26572
+ this.numberFormatSelect = null;
26573
+ this.currencyFormatSelect = null;
26574
+ this.dateFormatSelect = null;
26575
+ this.positionHint = null;
26576
+ this.numberFormatGroup = null;
26577
+ this.currencyFormatGroup = null;
26578
+ this.dateFormatGroup = null;
26579
+ this.currentField = null;
26580
+ this.onApplyCallback = options.onApply;
26581
+ }
26582
+ attach(options) {
26583
+ super.attach(options);
26584
+ if (this.editor) {
26585
+ // Listen for field selection events
26586
+ const selectionHandler = (event) => {
26587
+ if (event.type === 'field' && event.field) {
26588
+ this.showField(event.field);
26589
+ }
26590
+ else if (!event.type || event.type !== 'field') ;
26591
+ };
26592
+ this.editor.on('selection-change', selectionHandler);
26593
+ this.eventCleanup.push(() => {
26594
+ this.editor?.off('selection-change', selectionHandler);
26595
+ });
26596
+ }
26597
+ }
26598
+ createContent() {
26599
+ const container = document.createElement('div');
26600
+ // Field name input
26601
+ this.fieldNameInput = this.createTextInput({ placeholder: 'Field name' });
26602
+ container.appendChild(this.createFormGroup('Field Name', this.fieldNameInput));
26603
+ // Default value input
26604
+ this.fieldDefaultInput = this.createTextInput({ placeholder: 'Default value (optional)' });
26605
+ container.appendChild(this.createFormGroup('Default Value', this.fieldDefaultInput));
26606
+ // Value type select
26607
+ this.valueTypeSelect = this.createSelect([
26608
+ { value: '', label: '(None)' },
26609
+ { value: 'number', label: 'Number' },
26610
+ { value: 'currency', label: 'Currency' },
26611
+ { value: 'date', label: 'Date' }
26612
+ ]);
26613
+ this.addImmediateApplyListener(this.valueTypeSelect, () => this.updateFormatGroups());
26614
+ container.appendChild(this.createFormGroup('Value Type', this.valueTypeSelect));
26615
+ // Number format group
26616
+ this.numberFormatGroup = this.createSection();
26617
+ this.numberFormatGroup.style.display = 'none';
26618
+ this.numberFormatSelect = this.createSelect([
26619
+ { value: '0', label: 'Integer (0)' },
26620
+ { value: '0.00', label: 'Two decimals (0.00)' },
26621
+ { value: '0,0', label: 'Thousands separator (0,0)' },
26622
+ { value: '0,0.00', label: 'Thousands + decimals (0,0.00)' }
26623
+ ]);
26624
+ this.numberFormatGroup.appendChild(this.createFormGroup('Number Format', this.numberFormatSelect));
26625
+ container.appendChild(this.numberFormatGroup);
26626
+ // Currency format group
26627
+ this.currencyFormatGroup = this.createSection();
26628
+ this.currencyFormatGroup.style.display = 'none';
26629
+ this.currencyFormatSelect = this.createSelect([
26630
+ { value: 'USD', label: 'USD ($)' },
26631
+ { value: 'EUR', label: 'EUR' },
26632
+ { value: 'GBP', label: 'GBP' },
26633
+ { value: 'JPY', label: 'JPY' }
26634
+ ]);
26635
+ this.currencyFormatGroup.appendChild(this.createFormGroup('Currency', this.currencyFormatSelect));
26636
+ container.appendChild(this.currencyFormatGroup);
26637
+ // Date format group
26638
+ this.dateFormatGroup = this.createSection();
26639
+ this.dateFormatGroup.style.display = 'none';
26640
+ this.dateFormatSelect = this.createSelect([
26641
+ { value: 'MMMM D, YYYY', label: 'January 1, 2026' },
26642
+ { value: 'MM/DD/YYYY', label: '01/01/2026' },
26643
+ { value: 'DD/MM/YYYY', label: '01/01/2026 (EU)' },
26644
+ { value: 'YYYY-MM-DD', label: '2026-01-01 (ISO)' }
26645
+ ]);
26646
+ this.dateFormatGroup.appendChild(this.createFormGroup('Date Format', this.dateFormatSelect));
26647
+ container.appendChild(this.dateFormatGroup);
26648
+ // Apply button
26649
+ const applyBtn = this.createButton('Apply Changes', { variant: 'primary' });
26650
+ this.addButtonListener(applyBtn, () => this.applyChanges());
26651
+ container.appendChild(applyBtn);
26652
+ // Position hint
26653
+ this.positionHint = this.createHint('');
26654
+ container.appendChild(this.positionHint);
26655
+ return container;
26656
+ }
26657
+ updateFormatGroups() {
26658
+ const valueType = this.valueTypeSelect?.value || '';
26659
+ if (this.numberFormatGroup) {
26660
+ this.numberFormatGroup.style.display = valueType === 'number' ? 'block' : 'none';
26661
+ }
26662
+ if (this.currencyFormatGroup) {
26663
+ this.currencyFormatGroup.style.display = valueType === 'currency' ? 'block' : 'none';
26664
+ }
26665
+ if (this.dateFormatGroup) {
26666
+ this.dateFormatGroup.style.display = valueType === 'date' ? 'block' : 'none';
26667
+ }
26668
+ }
26669
+ /**
26670
+ * Show the pane with the given field.
26671
+ */
26672
+ showField(field) {
26673
+ this.currentField = field;
26674
+ if (this.fieldNameInput) {
26675
+ this.fieldNameInput.value = field.fieldName;
26676
+ }
26677
+ if (this.fieldDefaultInput) {
26678
+ this.fieldDefaultInput.value = field.defaultValue || '';
26679
+ }
26680
+ if (this.positionHint) {
26681
+ this.positionHint.textContent = `Field at position ${field.textIndex}`;
26682
+ }
26683
+ // Populate format options
26684
+ if (this.valueTypeSelect) {
26685
+ this.valueTypeSelect.value = field.formatConfig?.valueType || '';
26686
+ }
26687
+ if (this.numberFormatSelect && field.formatConfig?.numberFormat) {
26688
+ this.numberFormatSelect.value = field.formatConfig.numberFormat;
26689
+ }
26690
+ if (this.currencyFormatSelect && field.formatConfig?.currencyFormat) {
26691
+ this.currencyFormatSelect.value = field.formatConfig.currencyFormat;
26692
+ }
26693
+ if (this.dateFormatSelect && field.formatConfig?.dateFormat) {
26694
+ this.dateFormatSelect.value = field.formatConfig.dateFormat;
26695
+ }
26696
+ this.updateFormatGroups();
26697
+ this.show();
26698
+ }
26699
+ /**
26700
+ * Hide the pane and clear the current field.
26701
+ */
26702
+ hideField() {
26703
+ this.currentField = null;
26704
+ this.hide();
26705
+ }
26706
+ applyChanges() {
26707
+ if (!this.editor || !this.currentField) {
26708
+ this.onApplyCallback?.(false, new Error('No field selected'));
26709
+ return;
26710
+ }
26711
+ const fieldName = this.fieldNameInput?.value.trim();
26712
+ if (!fieldName) {
26713
+ this.onApplyCallback?.(false, new Error('Field name cannot be empty'));
26714
+ return;
26715
+ }
26716
+ const updates = {};
26717
+ if (fieldName !== this.currentField.fieldName) {
26718
+ updates.fieldName = fieldName;
26719
+ }
26720
+ const defaultValue = this.fieldDefaultInput?.value || undefined;
26721
+ if (defaultValue !== this.currentField.defaultValue) {
26722
+ updates.defaultValue = defaultValue;
26723
+ }
26724
+ // Build format config
26725
+ const valueType = this.valueTypeSelect?.value;
26726
+ if (valueType) {
26727
+ const formatConfig = {
26728
+ valueType: valueType
26729
+ };
26730
+ if (valueType === 'number' && this.numberFormatSelect?.value) {
26731
+ formatConfig.numberFormat = this.numberFormatSelect.value;
26732
+ }
26733
+ else if (valueType === 'currency' && this.currencyFormatSelect?.value) {
26734
+ formatConfig.currencyFormat = this.currencyFormatSelect.value;
26735
+ }
26736
+ else if (valueType === 'date' && this.dateFormatSelect?.value) {
26737
+ formatConfig.dateFormat = this.dateFormatSelect.value;
26738
+ }
26739
+ updates.formatConfig = formatConfig;
26740
+ }
26741
+ else if (this.currentField.formatConfig) {
26742
+ updates.formatConfig = undefined;
26743
+ }
26744
+ if (Object.keys(updates).length === 0) {
26745
+ return; // No changes
26746
+ }
26747
+ try {
26748
+ const success = this.editor.updateField(this.currentField.textIndex, updates);
26749
+ if (success) {
26750
+ // Update the current field reference
26751
+ this.currentField = this.editor.getFieldAt(this.currentField.textIndex) || null;
26752
+ this.onApplyCallback?.(true);
26753
+ }
26754
+ else {
26755
+ this.onApplyCallback?.(false, new Error('Failed to update field'));
26756
+ }
26757
+ }
26758
+ catch (error) {
26759
+ this.onApplyCallback?.(false, error instanceof Error ? error : new Error(String(error)));
26760
+ }
26761
+ }
26762
+ /**
26763
+ * Get the currently selected field.
26764
+ */
26765
+ getCurrentField() {
26766
+ return this.currentField;
26767
+ }
26768
+ /**
26769
+ * Check if a field is currently selected.
26770
+ */
26771
+ hasField() {
26772
+ return this.currentField !== null;
26773
+ }
26774
+ /**
26775
+ * Update the pane from current editor state.
26776
+ */
26777
+ update() {
26778
+ // Field pane doesn't auto-update - it's driven by selection events
26779
+ }
26780
+ }
26781
+
26782
+ /**
26783
+ * RepeatingSectionPane - Edit repeating section (loop) properties.
26784
+ *
26785
+ * Shows:
26786
+ * - Field path (array property in merge data)
26787
+ * - Position information
26788
+ *
26789
+ * Uses the PCEditor public API:
26790
+ * - editor.getRepeatingSection()
26791
+ * - editor.updateRepeatingSectionFieldPath()
26792
+ * - editor.removeRepeatingSection()
26793
+ */
26794
+ class RepeatingSectionPane extends BasePane {
26795
+ constructor(id = 'repeating-section', options = {}) {
26796
+ super(id, { className: 'pc-pane-repeating-section', ...options });
26797
+ this.fieldPathInput = null;
26798
+ this.positionHint = null;
26799
+ this.currentSection = null;
26800
+ this.onApplyCallback = options.onApply;
26801
+ this.onRemoveCallback = options.onRemove;
26802
+ }
26803
+ attach(options) {
26804
+ super.attach(options);
26805
+ if (this.editor) {
26806
+ // Listen for repeating section selection
26807
+ const selectionHandler = (event) => {
26808
+ if (event.type === 'repeating-section' && event.sectionId) {
26809
+ const section = this.editor?.getRepeatingSection(event.sectionId);
26810
+ if (section) {
26811
+ this.showSection(section);
26812
+ }
26813
+ }
26814
+ };
26815
+ const removedHandler = () => {
26816
+ this.hideSection();
26817
+ };
26818
+ this.editor.on('selection-change', selectionHandler);
26819
+ this.editor.on('repeating-section-removed', removedHandler);
26820
+ this.eventCleanup.push(() => {
26821
+ this.editor?.off('selection-change', selectionHandler);
26822
+ this.editor?.off('repeating-section-removed', removedHandler);
26823
+ });
26824
+ }
26825
+ }
26826
+ createContent() {
26827
+ const container = document.createElement('div');
26828
+ // Field path input
26829
+ this.fieldPathInput = this.createTextInput({ placeholder: 'items' });
26830
+ container.appendChild(this.createFormGroup('Array Field Path', this.fieldPathInput, {
26831
+ hint: 'Path to array in merge data (e.g., "items" or "contact.addresses")'
26832
+ }));
26833
+ // Apply button
26834
+ const applyBtn = this.createButton('Apply Changes', { variant: 'primary' });
26835
+ this.addButtonListener(applyBtn, () => this.applyChanges());
26836
+ container.appendChild(applyBtn);
26837
+ // Remove button
26838
+ const removeBtn = this.createButton('Remove Loop', { variant: 'danger' });
26839
+ removeBtn.style.marginTop = '0.5rem';
26840
+ this.addButtonListener(removeBtn, () => this.removeSection());
26841
+ container.appendChild(removeBtn);
26842
+ // Position hint
26843
+ this.positionHint = this.createHint('');
26844
+ container.appendChild(this.positionHint);
26845
+ return container;
26846
+ }
26847
+ /**
26848
+ * Show the pane with the given section.
26849
+ */
26850
+ showSection(section) {
26851
+ this.currentSection = section;
26852
+ if (this.fieldPathInput) {
26853
+ this.fieldPathInput.value = section.fieldPath;
26854
+ }
26855
+ if (this.positionHint) {
26856
+ this.positionHint.textContent = `Loop from position ${section.startIndex} to ${section.endIndex}`;
26857
+ }
26858
+ this.show();
26859
+ }
26860
+ /**
26861
+ * Hide the pane and clear the current section.
26862
+ */
26863
+ hideSection() {
26864
+ this.currentSection = null;
26865
+ this.hide();
26866
+ }
26867
+ applyChanges() {
26868
+ if (!this.editor || !this.currentSection) {
26869
+ this.onApplyCallback?.(false, new Error('No section selected'));
26870
+ return;
26871
+ }
26872
+ const fieldPath = this.fieldPathInput?.value.trim();
26873
+ if (!fieldPath) {
26874
+ this.onApplyCallback?.(false, new Error('Field path cannot be empty'));
26875
+ return;
26876
+ }
26877
+ if (fieldPath === this.currentSection.fieldPath) {
26878
+ return; // No changes
26879
+ }
26880
+ try {
26881
+ const success = this.editor.updateRepeatingSectionFieldPath(this.currentSection.id, fieldPath);
26882
+ if (success) {
26883
+ // Update the current section reference
26884
+ this.currentSection = this.editor.getRepeatingSection(this.currentSection.id) || null;
26885
+ if (this.currentSection) {
26886
+ this.showSection(this.currentSection);
26887
+ }
26888
+ this.onApplyCallback?.(true);
26889
+ }
26890
+ else {
26891
+ this.onApplyCallback?.(false, new Error('Failed to update section'));
26892
+ }
26893
+ }
26894
+ catch (error) {
26895
+ this.onApplyCallback?.(false, error instanceof Error ? error : new Error(String(error)));
26896
+ }
26897
+ }
26898
+ removeSection() {
26899
+ if (!this.editor || !this.currentSection)
26900
+ return;
26901
+ try {
26902
+ this.editor.removeRepeatingSection(this.currentSection.id);
26903
+ this.hideSection();
26904
+ this.onRemoveCallback?.(true);
26905
+ }
26906
+ catch {
26907
+ this.onRemoveCallback?.(false);
26908
+ }
26909
+ }
26910
+ /**
26911
+ * Get the currently selected section.
26912
+ */
26913
+ getCurrentSection() {
26914
+ return this.currentSection;
26915
+ }
26916
+ /**
26917
+ * Check if a section is currently selected.
26918
+ */
26919
+ hasSection() {
26920
+ return this.currentSection !== null;
26921
+ }
26922
+ /**
26923
+ * Update the pane from current editor state.
26924
+ */
26925
+ update() {
26926
+ // Section pane doesn't auto-update - it's driven by selection events
26927
+ }
26928
+ }
26929
+
26930
+ /**
26931
+ * TableRowLoopPane - Edit table row loop properties.
26932
+ *
26933
+ * Shows:
26934
+ * - Field path (array property in merge data)
26935
+ * - Row range information
26936
+ *
26937
+ * Uses the TableObject API:
26938
+ * - table.getRowLoop()
26939
+ * - table.updateRowLoopFieldPath()
26940
+ * - table.removeRowLoop()
26941
+ */
26942
+ class TableRowLoopPane extends BasePane {
26943
+ constructor(id = 'table-row-loop', options = {}) {
26944
+ super(id, { className: 'pc-pane-table-row-loop', ...options });
26945
+ this.fieldPathInput = null;
26946
+ this.rangeHint = null;
26947
+ this.currentLoop = null;
26948
+ this.currentTable = null;
26949
+ this.onApplyCallback = options.onApply;
26950
+ this.onRemoveCallback = options.onRemove;
26951
+ }
26952
+ attach(options) {
26953
+ super.attach(options);
26954
+ // Table row loop pane is typically shown manually when a table's row loop is selected
26955
+ // The consumer is responsible for calling showLoop() with the table and loop
26956
+ }
26957
+ createContent() {
26958
+ const container = document.createElement('div');
26959
+ // Field path input
26960
+ this.fieldPathInput = this.createTextInput({ placeholder: 'items' });
26961
+ container.appendChild(this.createFormGroup('Array Field Path', this.fieldPathInput, {
26962
+ hint: 'Path to array in merge data (e.g., "items" or "orders")'
26963
+ }));
26964
+ // Apply button
26965
+ const applyBtn = this.createButton('Apply Changes', { variant: 'primary' });
26966
+ this.addButtonListener(applyBtn, () => this.applyChanges());
26967
+ container.appendChild(applyBtn);
26968
+ // Remove button
26969
+ const removeBtn = this.createButton('Remove Loop', { variant: 'danger' });
26970
+ removeBtn.style.marginTop = '0.5rem';
26971
+ this.addButtonListener(removeBtn, () => this.removeLoop());
26972
+ container.appendChild(removeBtn);
26973
+ // Range hint
26974
+ this.rangeHint = this.createHint('');
26975
+ container.appendChild(this.rangeHint);
26976
+ return container;
26977
+ }
26978
+ /**
26979
+ * Show the pane with the given table and loop.
26980
+ */
26981
+ showLoop(table, loop) {
26982
+ this.currentTable = table;
26983
+ this.currentLoop = loop;
26984
+ if (this.fieldPathInput) {
26985
+ this.fieldPathInput.value = loop.fieldPath;
26986
+ }
26987
+ if (this.rangeHint) {
26988
+ this.rangeHint.textContent = `Rows ${loop.startRowIndex} - ${loop.endRowIndex}`;
26989
+ }
26990
+ this.show();
26991
+ }
26992
+ /**
26993
+ * Hide the pane and clear current loop.
26994
+ */
26995
+ hideLoop() {
26996
+ this.currentTable = null;
26997
+ this.currentLoop = null;
26998
+ this.hide();
26999
+ }
27000
+ applyChanges() {
27001
+ if (!this.currentTable || !this.currentLoop) {
27002
+ this.onApplyCallback?.(false, new Error('No loop selected'));
27003
+ return;
27004
+ }
27005
+ const fieldPath = this.fieldPathInput?.value.trim();
27006
+ if (!fieldPath) {
27007
+ this.onApplyCallback?.(false, new Error('Field path cannot be empty'));
27008
+ return;
27009
+ }
27010
+ if (fieldPath === this.currentLoop.fieldPath) {
27011
+ return; // No changes
27012
+ }
27013
+ try {
27014
+ const success = this.currentTable.updateRowLoopFieldPath(this.currentLoop.id, fieldPath);
27015
+ if (success) {
27016
+ // Update the current loop reference
27017
+ this.currentLoop = this.currentTable.getRowLoop(this.currentLoop.id) || null;
27018
+ if (this.currentLoop) {
27019
+ this.showLoop(this.currentTable, this.currentLoop);
27020
+ }
27021
+ this.onApplyCallback?.(true);
27022
+ }
27023
+ else {
27024
+ this.onApplyCallback?.(false, new Error('Failed to update loop'));
27025
+ }
27026
+ }
27027
+ catch (error) {
27028
+ this.onApplyCallback?.(false, error instanceof Error ? error : new Error(String(error)));
27029
+ }
27030
+ }
27031
+ removeLoop() {
27032
+ if (!this.currentTable || !this.currentLoop)
27033
+ return;
27034
+ try {
27035
+ const success = this.currentTable.removeRowLoop(this.currentLoop.id);
27036
+ if (success) {
27037
+ this.hideLoop();
27038
+ this.onRemoveCallback?.(true);
27039
+ }
27040
+ else {
27041
+ this.onRemoveCallback?.(false);
27042
+ }
27043
+ }
27044
+ catch {
27045
+ this.onRemoveCallback?.(false);
27046
+ }
27047
+ }
27048
+ /**
27049
+ * Get the currently selected loop.
27050
+ */
27051
+ getCurrentLoop() {
27052
+ return this.currentLoop;
27053
+ }
27054
+ /**
27055
+ * Get the currently selected table.
27056
+ */
27057
+ getCurrentTable() {
27058
+ return this.currentTable;
27059
+ }
27060
+ /**
27061
+ * Check if a loop is currently selected.
27062
+ */
27063
+ hasLoop() {
27064
+ return this.currentLoop !== null;
27065
+ }
27066
+ /**
27067
+ * Update the pane from current editor state.
27068
+ */
27069
+ update() {
27070
+ // Table row loop pane doesn't auto-update - it's driven by showLoop() calls
27071
+ }
27072
+ }
27073
+
27074
+ /**
27075
+ * TextBoxPane - Edit text box properties.
27076
+ *
27077
+ * Shows:
27078
+ * - Position (inline, block, relative)
27079
+ * - Relative offset (for relative positioning)
27080
+ * - Background color
27081
+ * - Border (width, color, style)
27082
+ * - Padding
27083
+ *
27084
+ * Uses the PCEditor public API:
27085
+ * - editor.getSelectedTextBox()
27086
+ * - editor.updateTextBox()
27087
+ */
27088
+ class TextBoxPane extends BasePane {
27089
+ constructor(id = 'textbox', options = {}) {
27090
+ super(id, { className: 'pc-pane-textbox', ...options });
27091
+ this.positionSelect = null;
27092
+ this.offsetGroup = null;
27093
+ this.offsetXInput = null;
27094
+ this.offsetYInput = null;
27095
+ this.bgColorInput = null;
27096
+ this.borderWidthInput = null;
27097
+ this.borderColorInput = null;
27098
+ this.borderStyleSelect = null;
27099
+ this.paddingInput = null;
27100
+ this.currentTextBox = null;
27101
+ this.onApplyCallback = options.onApply;
27102
+ }
27103
+ attach(options) {
27104
+ super.attach(options);
27105
+ if (this.editor) {
27106
+ // Listen for selection changes
27107
+ const updateHandler = () => this.updateFromSelection();
27108
+ this.editor.on('selection-change', updateHandler);
27109
+ this.editor.on('textbox-updated', updateHandler);
27110
+ this.eventCleanup.push(() => {
27111
+ this.editor?.off('selection-change', updateHandler);
27112
+ this.editor?.off('textbox-updated', updateHandler);
27113
+ });
27114
+ // Initial update
27115
+ this.updateFromSelection();
27116
+ }
27117
+ }
27118
+ createContent() {
27119
+ const container = document.createElement('div');
27120
+ // Position section
27121
+ const positionSection = this.createSection('Position');
27122
+ this.positionSelect = this.createSelect([
27123
+ { value: 'inline', label: 'Inline' },
27124
+ { value: 'block', label: 'Block' },
27125
+ { value: 'relative', label: 'Relative' }
27126
+ ], 'inline');
27127
+ this.addImmediateApplyListener(this.positionSelect, () => this.updateOffsetVisibility());
27128
+ positionSection.appendChild(this.createFormGroup('Type', this.positionSelect));
27129
+ // Offset group (only visible for relative positioning)
27130
+ this.offsetGroup = document.createElement('div');
27131
+ this.offsetGroup.style.display = 'none';
27132
+ const offsetRow = this.createRow();
27133
+ this.offsetXInput = this.createNumberInput({ value: 0 });
27134
+ this.offsetYInput = this.createNumberInput({ value: 0 });
27135
+ offsetRow.appendChild(this.createFormGroup('X', this.offsetXInput, { inline: true }));
27136
+ offsetRow.appendChild(this.createFormGroup('Y', this.offsetYInput, { inline: true }));
27137
+ this.offsetGroup.appendChild(offsetRow);
27138
+ positionSection.appendChild(this.offsetGroup);
27139
+ container.appendChild(positionSection);
27140
+ // Background section
27141
+ const bgSection = this.createSection('Background');
27142
+ this.bgColorInput = this.createColorInput('#ffffff');
27143
+ bgSection.appendChild(this.createFormGroup('Color', this.bgColorInput));
27144
+ container.appendChild(bgSection);
27145
+ // Border section
27146
+ const borderSection = this.createSection('Border');
27147
+ const borderRow = this.createRow();
27148
+ this.borderWidthInput = this.createNumberInput({ min: 0, max: 10, value: 1 });
27149
+ this.borderColorInput = this.createColorInput('#cccccc');
27150
+ borderRow.appendChild(this.createFormGroup('Width', this.borderWidthInput, { inline: true }));
27151
+ borderRow.appendChild(this.createFormGroup('Color', this.borderColorInput, { inline: true }));
27152
+ borderSection.appendChild(borderRow);
27153
+ this.borderStyleSelect = this.createSelect([
27154
+ { value: 'solid', label: 'Solid' },
27155
+ { value: 'dashed', label: 'Dashed' },
27156
+ { value: 'dotted', label: 'Dotted' },
27157
+ { value: 'none', label: 'None' }
27158
+ ], 'solid');
27159
+ borderSection.appendChild(this.createFormGroup('Style', this.borderStyleSelect));
27160
+ container.appendChild(borderSection);
27161
+ // Padding section
27162
+ const paddingSection = this.createSection('Padding');
27163
+ this.paddingInput = this.createNumberInput({ min: 0, max: 50, value: 8 });
27164
+ paddingSection.appendChild(this.createFormGroup('All sides (px)', this.paddingInput));
27165
+ container.appendChild(paddingSection);
27166
+ // Apply button
27167
+ const applyBtn = this.createButton('Apply Changes', { variant: 'primary' });
27168
+ this.addButtonListener(applyBtn, () => this.applyChanges());
27169
+ container.appendChild(applyBtn);
27170
+ return container;
27171
+ }
27172
+ updateFromSelection() {
27173
+ if (!this.editor)
27174
+ return;
27175
+ const textBox = this.editor.getSelectedTextBox?.();
27176
+ if (textBox && !textBox.editing) {
27177
+ this.showTextBox(textBox);
27178
+ }
27179
+ else {
27180
+ this.hideTextBox();
27181
+ }
27182
+ }
27183
+ /**
27184
+ * Show the pane with the given text box.
27185
+ */
27186
+ showTextBox(textBox) {
27187
+ this.currentTextBox = textBox;
27188
+ // Populate position
27189
+ if (this.positionSelect) {
27190
+ this.positionSelect.value = textBox.position || 'inline';
27191
+ }
27192
+ this.updateOffsetVisibility();
27193
+ // Populate offset
27194
+ if (this.offsetXInput) {
27195
+ this.offsetXInput.value = String(textBox.relativeOffset?.x ?? 0);
27196
+ }
27197
+ if (this.offsetYInput) {
27198
+ this.offsetYInput.value = String(textBox.relativeOffset?.y ?? 0);
27199
+ }
27200
+ // Populate background
27201
+ if (this.bgColorInput) {
27202
+ this.bgColorInput.value = textBox.backgroundColor || '#ffffff';
27203
+ }
27204
+ // Populate border (use first side with non-none style)
27205
+ const border = textBox.border;
27206
+ const activeBorder = border.top.style !== 'none' ? border.top :
27207
+ border.right.style !== 'none' ? border.right :
27208
+ border.bottom.style !== 'none' ? border.bottom :
27209
+ border.left.style !== 'none' ? border.left : border.top;
27210
+ if (this.borderWidthInput) {
27211
+ this.borderWidthInput.value = String(activeBorder.width);
27212
+ }
27213
+ if (this.borderColorInput) {
27214
+ this.borderColorInput.value = activeBorder.color;
27215
+ }
27216
+ if (this.borderStyleSelect) {
27217
+ this.borderStyleSelect.value = activeBorder.style;
27218
+ }
27219
+ // Populate padding
27220
+ if (this.paddingInput) {
27221
+ this.paddingInput.value = String(textBox.padding ?? 8);
27222
+ }
27223
+ this.show();
27224
+ }
27225
+ /**
27226
+ * Hide the pane and clear current text box.
27227
+ */
27228
+ hideTextBox() {
27229
+ this.currentTextBox = null;
27230
+ this.hide();
27231
+ }
27232
+ updateOffsetVisibility() {
27233
+ if (this.offsetGroup && this.positionSelect) {
27234
+ this.offsetGroup.style.display = this.positionSelect.value === 'relative' ? 'block' : 'none';
27235
+ }
27236
+ }
27237
+ applyChanges() {
27238
+ if (!this.editor || !this.currentTextBox) {
27239
+ this.onApplyCallback?.(false, new Error('No text box selected'));
27240
+ return;
27241
+ }
27242
+ const updates = {};
27243
+ // Position
27244
+ if (this.positionSelect) {
27245
+ updates.position = this.positionSelect.value;
27246
+ }
27247
+ // Relative offset
27248
+ if (this.positionSelect?.value === 'relative') {
27249
+ updates.relativeOffset = {
27250
+ x: parseInt(this.offsetXInput?.value || '0', 10),
27251
+ y: parseInt(this.offsetYInput?.value || '0', 10)
27252
+ };
27253
+ }
27254
+ // Background color
27255
+ if (this.bgColorInput) {
27256
+ updates.backgroundColor = this.bgColorInput.value;
27257
+ }
27258
+ // Border
27259
+ const width = parseInt(this.borderWidthInput?.value || '1', 10);
27260
+ const color = this.borderColorInput?.value || '#cccccc';
27261
+ const style = (this.borderStyleSelect?.value || 'solid');
27262
+ const borderSide = { width, color, style };
27263
+ updates.border = {
27264
+ top: { ...borderSide },
27265
+ right: { ...borderSide },
27266
+ bottom: { ...borderSide },
27267
+ left: { ...borderSide }
27268
+ };
27269
+ // Padding
27270
+ if (this.paddingInput) {
27271
+ updates.padding = parseInt(this.paddingInput.value, 10);
27272
+ }
27273
+ try {
27274
+ const success = this.editor.updateTextBox(this.currentTextBox.id, updates);
27275
+ if (success) {
27276
+ this.onApplyCallback?.(true);
27277
+ }
27278
+ else {
27279
+ this.onApplyCallback?.(false, new Error('Failed to update text box'));
27280
+ }
27281
+ }
27282
+ catch (error) {
27283
+ this.onApplyCallback?.(false, error instanceof Error ? error : new Error(String(error)));
27284
+ }
27285
+ }
27286
+ /**
27287
+ * Get the currently selected text box.
27288
+ */
27289
+ getCurrentTextBox() {
27290
+ return this.currentTextBox;
27291
+ }
27292
+ /**
27293
+ * Check if a text box is currently selected.
27294
+ */
27295
+ hasTextBox() {
27296
+ return this.currentTextBox !== null;
27297
+ }
27298
+ /**
27299
+ * Update the pane from current editor state.
27300
+ */
27301
+ update() {
27302
+ this.updateFromSelection();
27303
+ }
27304
+ }
27305
+
27306
+ /**
27307
+ * ImagePane - Edit image properties.
27308
+ *
27309
+ * Shows:
27310
+ * - Position (inline, block, relative)
27311
+ * - Relative offset (for relative positioning)
27312
+ * - Fit mode (contain, cover, fill, none, tile)
27313
+ * - Resize mode (free, locked-aspect-ratio)
27314
+ * - Alt text
27315
+ * - Source file picker
27316
+ *
27317
+ * Uses the PCEditor public API:
27318
+ * - editor.getSelectedImage()
27319
+ * - editor.updateImage()
27320
+ * - editor.setImageSource()
27321
+ */
27322
+ class ImagePane extends BasePane {
27323
+ constructor(id = 'image', options = {}) {
27324
+ super(id, { className: 'pc-pane-image', ...options });
27325
+ this.positionSelect = null;
27326
+ this.offsetGroup = null;
27327
+ this.offsetXInput = null;
27328
+ this.offsetYInput = null;
27329
+ this.fitModeSelect = null;
27330
+ this.resizeModeSelect = null;
27331
+ this.altTextInput = null;
27332
+ this.fileInput = null;
27333
+ this.currentImage = null;
27334
+ this.maxImageWidth = options.maxImageWidth ?? 400;
27335
+ this.maxImageHeight = options.maxImageHeight ?? 400;
27336
+ this.onApplyCallback = options.onApply;
27337
+ }
27338
+ attach(options) {
27339
+ super.attach(options);
27340
+ if (this.editor) {
27341
+ // Listen for selection changes
27342
+ const updateHandler = () => this.updateFromSelection();
27343
+ this.editor.on('selection-change', updateHandler);
27344
+ this.editor.on('image-updated', updateHandler);
27345
+ this.eventCleanup.push(() => {
27346
+ this.editor?.off('selection-change', updateHandler);
27347
+ this.editor?.off('image-updated', updateHandler);
27348
+ });
27349
+ // Initial update
27350
+ this.updateFromSelection();
27351
+ }
27352
+ }
27353
+ createContent() {
27354
+ const container = document.createElement('div');
27355
+ // Position section
27356
+ const positionSection = this.createSection('Position');
27357
+ this.positionSelect = this.createSelect([
27358
+ { value: 'inline', label: 'Inline' },
27359
+ { value: 'block', label: 'Block' },
27360
+ { value: 'relative', label: 'Relative' }
27361
+ ], 'inline');
27362
+ this.addImmediateApplyListener(this.positionSelect, () => this.updateOffsetVisibility());
27363
+ positionSection.appendChild(this.createFormGroup('Type', this.positionSelect));
27364
+ // Offset group (only visible for relative positioning)
27365
+ this.offsetGroup = document.createElement('div');
27366
+ this.offsetGroup.style.display = 'none';
27367
+ const offsetRow = this.createRow();
27368
+ this.offsetXInput = this.createNumberInput({ value: 0 });
27369
+ this.offsetYInput = this.createNumberInput({ value: 0 });
27370
+ offsetRow.appendChild(this.createFormGroup('X', this.offsetXInput, { inline: true }));
27371
+ offsetRow.appendChild(this.createFormGroup('Y', this.offsetYInput, { inline: true }));
27372
+ this.offsetGroup.appendChild(offsetRow);
27373
+ positionSection.appendChild(this.offsetGroup);
27374
+ container.appendChild(positionSection);
27375
+ // Fit mode section
27376
+ const fitSection = this.createSection('Display');
27377
+ this.fitModeSelect = this.createSelect([
27378
+ { value: 'contain', label: 'Contain' },
27379
+ { value: 'cover', label: 'Cover' },
27380
+ { value: 'fill', label: 'Fill' },
27381
+ { value: 'none', label: 'None (original size)' },
27382
+ { value: 'tile', label: 'Tile' }
27383
+ ], 'contain');
27384
+ fitSection.appendChild(this.createFormGroup('Fit Mode', this.fitModeSelect));
27385
+ this.resizeModeSelect = this.createSelect([
27386
+ { value: 'locked-aspect-ratio', label: 'Lock Aspect Ratio' },
27387
+ { value: 'free', label: 'Free Resize' }
27388
+ ], 'locked-aspect-ratio');
27389
+ fitSection.appendChild(this.createFormGroup('Resize Mode', this.resizeModeSelect));
27390
+ container.appendChild(fitSection);
27391
+ // Alt text section
27392
+ const altSection = this.createSection('Accessibility');
27393
+ this.altTextInput = this.createTextInput({ placeholder: 'Description of the image' });
27394
+ altSection.appendChild(this.createFormGroup('Alt Text', this.altTextInput));
27395
+ container.appendChild(altSection);
27396
+ // Source section
27397
+ const sourceSection = this.createSection('Source');
27398
+ this.fileInput = document.createElement('input');
27399
+ this.fileInput.type = 'file';
27400
+ this.fileInput.accept = 'image/*';
27401
+ this.fileInput.style.display = 'none';
27402
+ this.fileInput.addEventListener('change', (e) => this.handleFileChange(e));
27403
+ sourceSection.appendChild(this.fileInput);
27404
+ const changeSourceBtn = this.createButton('Change Image...');
27405
+ this.addButtonListener(changeSourceBtn, () => this.fileInput?.click());
27406
+ sourceSection.appendChild(changeSourceBtn);
27407
+ container.appendChild(sourceSection);
27408
+ // Apply button
27409
+ const applyBtn = this.createButton('Apply Changes', { variant: 'primary' });
27410
+ this.addButtonListener(applyBtn, () => this.applyChanges());
27411
+ container.appendChild(applyBtn);
27412
+ return container;
27413
+ }
27414
+ updateFromSelection() {
27415
+ if (!this.editor)
27416
+ return;
27417
+ const image = this.editor.getSelectedImage?.();
27418
+ if (image) {
27419
+ this.showImage(image);
27420
+ }
27421
+ else {
27422
+ this.hideImage();
27423
+ }
27424
+ }
27425
+ /**
27426
+ * Show the pane with the given image.
27427
+ */
27428
+ showImage(image) {
27429
+ this.currentImage = image;
27430
+ // Populate position
27431
+ if (this.positionSelect) {
27432
+ this.positionSelect.value = image.position || 'inline';
27433
+ }
27434
+ this.updateOffsetVisibility();
27435
+ // Populate offset
27436
+ if (this.offsetXInput) {
27437
+ this.offsetXInput.value = String(image.relativeOffset?.x ?? 0);
27438
+ }
27439
+ if (this.offsetYInput) {
27440
+ this.offsetYInput.value = String(image.relativeOffset?.y ?? 0);
27441
+ }
27442
+ // Populate fit mode
27443
+ if (this.fitModeSelect) {
27444
+ this.fitModeSelect.value = image.fit || 'contain';
27445
+ }
27446
+ // Populate resize mode
27447
+ if (this.resizeModeSelect) {
27448
+ this.resizeModeSelect.value = image.resizeMode || 'locked-aspect-ratio';
27449
+ }
27450
+ // Populate alt text
27451
+ if (this.altTextInput) {
27452
+ this.altTextInput.value = image.alt || '';
27453
+ }
27454
+ this.show();
27455
+ }
27456
+ /**
27457
+ * Hide the pane and clear current image.
27458
+ */
27459
+ hideImage() {
27460
+ this.currentImage = null;
27461
+ this.hide();
27462
+ }
27463
+ updateOffsetVisibility() {
27464
+ if (this.offsetGroup && this.positionSelect) {
27465
+ this.offsetGroup.style.display = this.positionSelect.value === 'relative' ? 'block' : 'none';
27466
+ }
27467
+ }
27468
+ handleFileChange(event) {
27469
+ if (!this.editor || !this.currentImage)
27470
+ return;
27471
+ const input = event.target;
27472
+ const file = input.files?.[0];
27473
+ if (!file)
27474
+ return;
27475
+ const reader = new FileReader();
27476
+ reader.onload = (e) => {
27477
+ const dataUrl = e.target?.result;
27478
+ if (dataUrl && this.currentImage && this.editor) {
27479
+ this.editor.setImageSource(this.currentImage.id, dataUrl, {
27480
+ maxWidth: this.maxImageWidth,
27481
+ maxHeight: this.maxImageHeight
27482
+ });
27483
+ }
27484
+ };
27485
+ reader.readAsDataURL(file);
27486
+ // Reset file input so the same file can be selected again
27487
+ input.value = '';
27488
+ }
27489
+ applyChanges() {
27490
+ if (!this.editor || !this.currentImage) {
27491
+ this.onApplyCallback?.(false, new Error('No image selected'));
27492
+ return;
27493
+ }
27494
+ const updates = {};
27495
+ // Position
27496
+ if (this.positionSelect) {
27497
+ updates.position = this.positionSelect.value;
27498
+ }
27499
+ // Relative offset
27500
+ if (this.positionSelect?.value === 'relative') {
27501
+ updates.relativeOffset = {
27502
+ x: parseInt(this.offsetXInput?.value || '0', 10),
27503
+ y: parseInt(this.offsetYInput?.value || '0', 10)
27504
+ };
27505
+ }
27506
+ // Fit mode
27507
+ if (this.fitModeSelect) {
27508
+ updates.fit = this.fitModeSelect.value;
27509
+ }
27510
+ // Resize mode
27511
+ if (this.resizeModeSelect) {
27512
+ updates.resizeMode = this.resizeModeSelect.value;
27513
+ }
27514
+ // Alt text
27515
+ if (this.altTextInput) {
27516
+ updates.alt = this.altTextInput.value;
27517
+ }
27518
+ try {
27519
+ const success = this.editor.updateImage(this.currentImage.id, updates);
27520
+ if (success) {
27521
+ this.onApplyCallback?.(true);
27522
+ }
27523
+ else {
27524
+ this.onApplyCallback?.(false, new Error('Failed to update image'));
27525
+ }
27526
+ }
27527
+ catch (error) {
27528
+ this.onApplyCallback?.(false, error instanceof Error ? error : new Error(String(error)));
27529
+ }
27530
+ }
27531
+ /**
27532
+ * Get the currently selected image.
27533
+ */
27534
+ getCurrentImage() {
27535
+ return this.currentImage;
27536
+ }
27537
+ /**
27538
+ * Check if an image is currently selected.
27539
+ */
27540
+ hasImage() {
27541
+ return this.currentImage !== null;
27542
+ }
27543
+ /**
27544
+ * Update the pane from current editor state.
27545
+ */
27546
+ update() {
27547
+ this.updateFromSelection();
27548
+ }
27549
+ }
27550
+
27551
+ /**
27552
+ * TablePane - Edit table properties.
27553
+ *
27554
+ * Shows:
27555
+ * - Table structure (row/column count)
27556
+ * - Row/column insertion/removal
27557
+ * - Header rows/columns
27558
+ * - Default cell padding and border color
27559
+ * - Cell-specific formatting (background, borders)
27560
+ *
27561
+ * Uses the PCEditor public API:
27562
+ * - editor.getFocusedTable()
27563
+ * - editor.tableInsertRow()
27564
+ * - editor.tableRemoveRow()
27565
+ * - editor.tableInsertColumn()
27566
+ * - editor.tableRemoveColumn()
27567
+ *
27568
+ * And TableObject methods:
27569
+ * - table.setHeaderRowCount()
27570
+ * - table.setHeaderColumnCount()
27571
+ * - table.getCell()
27572
+ * - table.getCellsInRange()
27573
+ */
27574
+ class TablePane extends BasePane {
27575
+ constructor(id = 'table', options = {}) {
27576
+ super(id, { className: 'pc-pane-table', ...options });
27577
+ // Structure info
27578
+ this.rowCountDisplay = null;
27579
+ this.colCountDisplay = null;
27580
+ this.cellSelectionDisplay = null;
27581
+ // Header controls
27582
+ this.headerRowInput = null;
27583
+ this.headerColInput = null;
27584
+ // Default controls
27585
+ this.defaultPaddingInput = null;
27586
+ this.defaultBorderColorInput = null;
27587
+ // Cell formatting controls
27588
+ this.cellBgColorInput = null;
27589
+ this.borderTopCheck = null;
27590
+ this.borderRightCheck = null;
27591
+ this.borderBottomCheck = null;
27592
+ this.borderLeftCheck = null;
27593
+ this.borderWidthInput = null;
27594
+ this.borderColorInput = null;
27595
+ this.borderStyleSelect = null;
27596
+ this.currentTable = null;
27597
+ this.onApplyCallback = options.onApply;
27598
+ }
27599
+ attach(options) {
27600
+ super.attach(options);
27601
+ if (this.editor) {
27602
+ // Listen for selection/focus changes
27603
+ const updateHandler = () => this.updateFromFocusedTable();
27604
+ this.editor.on('selection-change', updateHandler);
27605
+ this.editor.on('table-cell-focus', updateHandler);
27606
+ this.editor.on('table-cell-selection', updateHandler);
27607
+ this.eventCleanup.push(() => {
27608
+ this.editor?.off('selection-change', updateHandler);
27609
+ this.editor?.off('table-cell-focus', updateHandler);
27610
+ this.editor?.off('table-cell-selection', updateHandler);
27611
+ });
27612
+ // Initial update
27613
+ this.updateFromFocusedTable();
27614
+ }
27615
+ }
27616
+ createContent() {
27617
+ const container = document.createElement('div');
27618
+ // Structure section
27619
+ const structureSection = this.createSection('Structure');
27620
+ const structureInfo = document.createElement('div');
27621
+ structureInfo.className = 'pc-pane-info-list';
27622
+ this.rowCountDisplay = document.createElement('span');
27623
+ this.colCountDisplay = document.createElement('span');
27624
+ const rowInfo = document.createElement('div');
27625
+ rowInfo.className = 'pc-pane-info';
27626
+ rowInfo.innerHTML = '<span class="pc-pane-info-label">Rows</span>';
27627
+ rowInfo.appendChild(this.rowCountDisplay);
27628
+ const colInfo = document.createElement('div');
27629
+ colInfo.className = 'pc-pane-info';
27630
+ colInfo.innerHTML = '<span class="pc-pane-info-label">Columns</span>';
27631
+ colInfo.appendChild(this.colCountDisplay);
27632
+ structureInfo.appendChild(rowInfo);
27633
+ structureInfo.appendChild(colInfo);
27634
+ structureSection.appendChild(structureInfo);
27635
+ // Row/column buttons
27636
+ const structureBtns = this.createButtonGroup();
27637
+ const addRowBtn = this.createButton('+ Row');
27638
+ this.addButtonListener(addRowBtn, () => this.insertRow());
27639
+ const removeRowBtn = this.createButton('- Row');
27640
+ this.addButtonListener(removeRowBtn, () => this.removeRow());
27641
+ const addColBtn = this.createButton('+ Column');
27642
+ this.addButtonListener(addColBtn, () => this.insertColumn());
27643
+ const removeColBtn = this.createButton('- Column');
27644
+ this.addButtonListener(removeColBtn, () => this.removeColumn());
27645
+ structureBtns.appendChild(addRowBtn);
27646
+ structureBtns.appendChild(removeRowBtn);
27647
+ structureBtns.appendChild(addColBtn);
27648
+ structureBtns.appendChild(removeColBtn);
27649
+ structureSection.appendChild(structureBtns);
27650
+ container.appendChild(structureSection);
27651
+ // Headers section
27652
+ const headersSection = this.createSection('Headers');
27653
+ const headerRow = this.createRow();
27654
+ this.headerRowInput = this.createNumberInput({ min: 0, max: 10, value: 0 });
27655
+ this.headerColInput = this.createNumberInput({ min: 0, max: 10, value: 0 });
27656
+ headerRow.appendChild(this.createFormGroup('Header Rows', this.headerRowInput, { inline: true }));
27657
+ headerRow.appendChild(this.createFormGroup('Header Cols', this.headerColInput, { inline: true }));
27658
+ headersSection.appendChild(headerRow);
27659
+ const applyHeadersBtn = this.createButton('Apply Headers');
27660
+ this.addButtonListener(applyHeadersBtn, () => this.applyHeaders());
27661
+ headersSection.appendChild(applyHeadersBtn);
27662
+ container.appendChild(headersSection);
27663
+ // Defaults section
27664
+ const defaultsSection = this.createSection('Defaults');
27665
+ const defaultsRow = this.createRow();
27666
+ this.defaultPaddingInput = this.createNumberInput({ min: 0, max: 20, value: 8 });
27667
+ this.defaultBorderColorInput = this.createColorInput('#cccccc');
27668
+ defaultsRow.appendChild(this.createFormGroup('Padding', this.defaultPaddingInput, { inline: true }));
27669
+ defaultsRow.appendChild(this.createFormGroup('Border', this.defaultBorderColorInput, { inline: true }));
27670
+ defaultsSection.appendChild(defaultsRow);
27671
+ const applyDefaultsBtn = this.createButton('Apply Defaults');
27672
+ this.addButtonListener(applyDefaultsBtn, () => this.applyDefaults());
27673
+ defaultsSection.appendChild(applyDefaultsBtn);
27674
+ container.appendChild(defaultsSection);
27675
+ // Cell formatting section
27676
+ const cellSection = this.createSection('Cell Formatting');
27677
+ this.cellSelectionDisplay = this.createHint('No cell selected');
27678
+ cellSection.appendChild(this.cellSelectionDisplay);
27679
+ // Background
27680
+ this.cellBgColorInput = this.createColorInput('#ffffff');
27681
+ cellSection.appendChild(this.createFormGroup('Background', this.cellBgColorInput));
27682
+ // Border checkboxes
27683
+ const borderChecks = document.createElement('div');
27684
+ borderChecks.className = 'pc-pane-row';
27685
+ borderChecks.style.flexWrap = 'wrap';
27686
+ borderChecks.style.gap = '4px';
27687
+ this.borderTopCheck = document.createElement('input');
27688
+ this.borderTopCheck.type = 'checkbox';
27689
+ this.borderTopCheck.checked = true;
27690
+ this.borderRightCheck = document.createElement('input');
27691
+ this.borderRightCheck.type = 'checkbox';
27692
+ this.borderRightCheck.checked = true;
27693
+ this.borderBottomCheck = document.createElement('input');
27694
+ this.borderBottomCheck.type = 'checkbox';
27695
+ this.borderBottomCheck.checked = true;
27696
+ this.borderLeftCheck = document.createElement('input');
27697
+ this.borderLeftCheck.type = 'checkbox';
27698
+ this.borderLeftCheck.checked = true;
27699
+ borderChecks.appendChild(this.createCheckbox('Top', true));
27700
+ borderChecks.appendChild(this.createCheckbox('Right', true));
27701
+ borderChecks.appendChild(this.createCheckbox('Bottom', true));
27702
+ borderChecks.appendChild(this.createCheckbox('Left', true));
27703
+ // Replace created checkboxes with our tracked ones
27704
+ const checkLabels = borderChecks.querySelectorAll('label');
27705
+ if (checkLabels[0])
27706
+ checkLabels[0].replaceChild(this.borderTopCheck, checkLabels[0].querySelector('input'));
27707
+ if (checkLabels[1])
27708
+ checkLabels[1].replaceChild(this.borderRightCheck, checkLabels[1].querySelector('input'));
27709
+ if (checkLabels[2])
27710
+ checkLabels[2].replaceChild(this.borderBottomCheck, checkLabels[2].querySelector('input'));
27711
+ if (checkLabels[3])
27712
+ checkLabels[3].replaceChild(this.borderLeftCheck, checkLabels[3].querySelector('input'));
27713
+ cellSection.appendChild(this.createFormGroup('Borders', borderChecks));
27714
+ // Border properties
27715
+ const borderPropsRow = this.createRow();
27716
+ this.borderWidthInput = this.createNumberInput({ min: 0, max: 5, value: 1 });
27717
+ this.borderColorInput = this.createColorInput('#cccccc');
27718
+ borderPropsRow.appendChild(this.createFormGroup('Width', this.borderWidthInput, { inline: true }));
27719
+ borderPropsRow.appendChild(this.createFormGroup('Color', this.borderColorInput, { inline: true }));
27720
+ cellSection.appendChild(borderPropsRow);
27721
+ this.borderStyleSelect = this.createSelect([
27722
+ { value: 'solid', label: 'Solid' },
27723
+ { value: 'dashed', label: 'Dashed' },
27724
+ { value: 'dotted', label: 'Dotted' },
27725
+ { value: 'none', label: 'None' }
27726
+ ], 'solid');
27727
+ cellSection.appendChild(this.createFormGroup('Style', this.borderStyleSelect));
27728
+ const applyCellBtn = this.createButton('Apply to Cell(s)', { variant: 'primary' });
27729
+ this.addButtonListener(applyCellBtn, () => this.applyCellFormatting());
27730
+ cellSection.appendChild(applyCellBtn);
27731
+ container.appendChild(cellSection);
27732
+ return container;
27733
+ }
27734
+ updateFromFocusedTable() {
27735
+ if (!this.editor)
27736
+ return;
27737
+ const table = this.editor.getFocusedTable();
27738
+ if (table) {
27739
+ this.showTable(table);
27740
+ }
27741
+ else {
27742
+ this.hideTable();
27743
+ }
27744
+ }
27745
+ /**
27746
+ * Show the pane with the given table.
27747
+ */
27748
+ showTable(table) {
27749
+ this.currentTable = table;
27750
+ // Update structure info
27751
+ if (this.rowCountDisplay) {
27752
+ this.rowCountDisplay.textContent = String(table.rowCount);
27753
+ this.rowCountDisplay.className = 'pc-pane-info-value';
27754
+ }
27755
+ if (this.colCountDisplay) {
27756
+ this.colCountDisplay.textContent = String(table.columnCount);
27757
+ this.colCountDisplay.className = 'pc-pane-info-value';
27758
+ }
27759
+ // Update header counts
27760
+ if (this.headerRowInput) {
27761
+ this.headerRowInput.value = String(table.headerRowCount);
27762
+ }
27763
+ if (this.headerColInput) {
27764
+ this.headerColInput.value = String(table.headerColumnCount);
27765
+ }
27766
+ // Update defaults
27767
+ if (this.defaultPaddingInput) {
27768
+ this.defaultPaddingInput.value = String(table.defaultCellPadding);
27769
+ }
27770
+ if (this.defaultBorderColorInput) {
27771
+ this.defaultBorderColorInput.value = table.defaultBorderColor;
27772
+ }
27773
+ // Update cell selection info
27774
+ this.updateCellSelectionInfo(table);
27775
+ this.show();
27776
+ }
27777
+ /**
27778
+ * Hide the pane and clear current table.
27779
+ */
27780
+ hideTable() {
27781
+ this.currentTable = null;
27782
+ this.hide();
27783
+ }
27784
+ updateCellSelectionInfo(table) {
27785
+ if (!this.cellSelectionDisplay)
27786
+ return;
27787
+ const focusedCell = table.focusedCell;
27788
+ const selectedRange = table.selectedRange;
27789
+ if (selectedRange) {
27790
+ const count = (selectedRange.end.row - selectedRange.start.row + 1) *
27791
+ (selectedRange.end.col - selectedRange.start.col + 1);
27792
+ this.cellSelectionDisplay.textContent = `${count} cells selected`;
27793
+ }
27794
+ else if (focusedCell) {
27795
+ this.cellSelectionDisplay.textContent = `Cell [${focusedCell.row}, ${focusedCell.col}]`;
27796
+ // Update cell formatting controls from focused cell
27797
+ const cell = table.getCell(focusedCell.row, focusedCell.col);
27798
+ if (cell) {
27799
+ if (this.cellBgColorInput) {
27800
+ this.cellBgColorInput.value = cell.backgroundColor || '#ffffff';
27801
+ }
27802
+ // Update border controls
27803
+ const border = cell.border;
27804
+ if (this.borderTopCheck)
27805
+ this.borderTopCheck.checked = border.top.style !== 'none';
27806
+ if (this.borderRightCheck)
27807
+ this.borderRightCheck.checked = border.right.style !== 'none';
27808
+ if (this.borderBottomCheck)
27809
+ this.borderBottomCheck.checked = border.bottom.style !== 'none';
27810
+ if (this.borderLeftCheck)
27811
+ this.borderLeftCheck.checked = border.left.style !== 'none';
27812
+ // Use first active border for properties
27813
+ const activeBorder = border.top.style !== 'none' ? border.top :
27814
+ border.right.style !== 'none' ? border.right :
27815
+ border.bottom.style !== 'none' ? border.bottom :
27816
+ border.left.style !== 'none' ? border.left : border.top;
27817
+ if (this.borderWidthInput)
27818
+ this.borderWidthInput.value = String(activeBorder.width);
27819
+ if (this.borderColorInput)
27820
+ this.borderColorInput.value = activeBorder.color;
27821
+ if (this.borderStyleSelect)
27822
+ this.borderStyleSelect.value = activeBorder.style;
27823
+ }
27824
+ }
27825
+ else {
27826
+ this.cellSelectionDisplay.textContent = 'No cell selected';
27827
+ }
27828
+ }
27829
+ insertRow() {
27830
+ if (!this.editor || !this.currentTable)
27831
+ return;
27832
+ const focusedCell = this.currentTable.focusedCell;
27833
+ const rowIndex = focusedCell ? focusedCell.row + 1 : this.currentTable.rowCount;
27834
+ this.editor.tableInsertRow(this.currentTable, rowIndex);
27835
+ this.updateFromFocusedTable();
27836
+ }
27837
+ removeRow() {
27838
+ if (!this.editor || !this.currentTable)
27839
+ return;
27840
+ const focusedCell = this.currentTable.focusedCell;
27841
+ if (focusedCell && this.currentTable.rowCount > 1) {
27842
+ this.editor.tableRemoveRow(this.currentTable, focusedCell.row);
27843
+ this.updateFromFocusedTable();
27844
+ }
27845
+ }
27846
+ insertColumn() {
27847
+ if (!this.editor || !this.currentTable)
27848
+ return;
27849
+ const focusedCell = this.currentTable.focusedCell;
27850
+ const colIndex = focusedCell ? focusedCell.col + 1 : this.currentTable.columnCount;
27851
+ this.editor.tableInsertColumn(this.currentTable, colIndex);
27852
+ this.updateFromFocusedTable();
27853
+ }
27854
+ removeColumn() {
27855
+ if (!this.editor || !this.currentTable)
27856
+ return;
27857
+ const focusedCell = this.currentTable.focusedCell;
27858
+ if (focusedCell && this.currentTable.columnCount > 1) {
27859
+ this.editor.tableRemoveColumn(this.currentTable, focusedCell.col);
27860
+ this.updateFromFocusedTable();
27861
+ }
27862
+ }
27863
+ applyHeaders() {
27864
+ if (!this.currentTable)
27865
+ return;
27866
+ if (this.headerRowInput) {
27867
+ const count = parseInt(this.headerRowInput.value, 10);
27868
+ this.currentTable.setHeaderRowCount(count);
27869
+ }
27870
+ if (this.headerColInput) {
27871
+ const count = parseInt(this.headerColInput.value, 10);
27872
+ this.currentTable.setHeaderColumnCount(count);
27873
+ }
27874
+ this.editor?.render();
27875
+ this.onApplyCallback?.(true);
27876
+ }
27877
+ applyDefaults() {
27878
+ if (!this.currentTable)
27879
+ return;
27880
+ if (this.defaultPaddingInput) {
27881
+ this.currentTable.defaultCellPadding = parseInt(this.defaultPaddingInput.value, 10);
27882
+ }
27883
+ if (this.defaultBorderColorInput) {
27884
+ this.currentTable.defaultBorderColor = this.defaultBorderColorInput.value;
27885
+ }
27886
+ this.editor?.render();
27887
+ this.onApplyCallback?.(true);
27888
+ }
27889
+ applyCellFormatting() {
27890
+ if (!this.currentTable)
27891
+ return;
27892
+ const focusedCell = this.currentTable.focusedCell;
27893
+ const selectedRange = this.currentTable.selectedRange;
27894
+ // Determine cells to update
27895
+ const cells = [];
27896
+ if (selectedRange) {
27897
+ for (let row = selectedRange.start.row; row <= selectedRange.end.row; row++) {
27898
+ for (let col = selectedRange.start.col; col <= selectedRange.end.col; col++) {
27899
+ cells.push({ row, col });
27900
+ }
27901
+ }
27902
+ }
27903
+ else if (focusedCell) {
27904
+ cells.push(focusedCell);
27905
+ }
27906
+ if (cells.length === 0)
27907
+ return;
27908
+ // Build border config
27909
+ const width = parseInt(this.borderWidthInput?.value || '1', 10);
27910
+ const color = this.borderColorInput?.value || '#cccccc';
27911
+ const style = (this.borderStyleSelect?.value || 'solid');
27912
+ const borderSide = { width, color, style };
27913
+ const noneBorder = { width: 0, color: '#000000', style: 'none' };
27914
+ const border = {
27915
+ top: this.borderTopCheck?.checked ? { ...borderSide } : { ...noneBorder },
27916
+ right: this.borderRightCheck?.checked ? { ...borderSide } : { ...noneBorder },
27917
+ bottom: this.borderBottomCheck?.checked ? { ...borderSide } : { ...noneBorder },
27918
+ left: this.borderLeftCheck?.checked ? { ...borderSide } : { ...noneBorder }
27919
+ };
27920
+ const bgColor = this.cellBgColorInput?.value;
27921
+ // Apply to each cell
27922
+ for (const { row, col } of cells) {
27923
+ const cell = this.currentTable.getCell(row, col);
27924
+ if (cell) {
27925
+ if (bgColor) {
27926
+ cell.backgroundColor = bgColor;
27927
+ }
27928
+ cell.border = border;
27929
+ }
27930
+ }
27931
+ this.editor?.render();
27932
+ this.onApplyCallback?.(true);
27933
+ }
27934
+ /**
27935
+ * Get the currently focused table.
27936
+ */
27937
+ getCurrentTable() {
27938
+ return this.currentTable;
27939
+ }
27940
+ /**
27941
+ * Check if a table is currently focused.
27942
+ */
27943
+ hasTable() {
27944
+ return this.currentTable !== null;
27945
+ }
27946
+ /**
27947
+ * Update the pane from current editor state.
27948
+ */
27949
+ update() {
27950
+ this.updateFromFocusedTable();
27951
+ }
27952
+ }
27953
+
25182
27954
  exports.BaseControl = BaseControl;
25183
27955
  exports.BaseEmbeddedObject = BaseEmbeddedObject;
27956
+ exports.BasePane = BasePane;
25184
27957
  exports.BaseTextRegion = BaseTextRegion;
25185
27958
  exports.BodyTextRegion = BodyTextRegion;
25186
27959
  exports.ClipboardManager = ClipboardManager;
@@ -25188,15 +27961,21 @@ exports.ContentAnalyzer = ContentAnalyzer;
25188
27961
  exports.DEFAULT_IMPORT_OPTIONS = DEFAULT_IMPORT_OPTIONS;
25189
27962
  exports.Document = Document;
25190
27963
  exports.DocumentBuilder = DocumentBuilder;
27964
+ exports.DocumentInfoPane = DocumentInfoPane;
27965
+ exports.DocumentSettingsPane = DocumentSettingsPane;
25191
27966
  exports.EmbeddedObjectFactory = EmbeddedObjectFactory;
25192
27967
  exports.EmbeddedObjectManager = EmbeddedObjectManager;
25193
27968
  exports.EventEmitter = EventEmitter;
25194
27969
  exports.FlowingTextContent = FlowingTextContent;
25195
27970
  exports.FooterTextRegion = FooterTextRegion;
27971
+ exports.FormattingPane = FormattingPane;
25196
27972
  exports.HeaderTextRegion = HeaderTextRegion;
25197
27973
  exports.HorizontalRuler = HorizontalRuler;
25198
27974
  exports.HtmlConverter = HtmlConverter;
27975
+ exports.HyperlinkPane = HyperlinkPane;
25199
27976
  exports.ImageObject = ImageObject;
27977
+ exports.ImagePane = ImagePane;
27978
+ exports.MergeDataPane = MergeDataPane;
25200
27979
  exports.PCEditor = PCEditor;
25201
27980
  exports.PDFImportError = PDFImportError;
25202
27981
  exports.PDFImporter = PDFImporter;
@@ -25204,16 +27983,22 @@ exports.PDFParser = PDFParser;
25204
27983
  exports.Page = Page;
25205
27984
  exports.RegionManager = RegionManager;
25206
27985
  exports.RepeatingSectionManager = RepeatingSectionManager;
27986
+ exports.RepeatingSectionPane = RepeatingSectionPane;
25207
27987
  exports.RulerControl = RulerControl;
25208
27988
  exports.SubstitutionFieldManager = SubstitutionFieldManager;
27989
+ exports.SubstitutionFieldPane = SubstitutionFieldPane;
25209
27990
  exports.TableCell = TableCell;
25210
27991
  exports.TableObject = TableObject;
27992
+ exports.TablePane = TablePane;
25211
27993
  exports.TableRow = TableRow;
27994
+ exports.TableRowLoopPane = TableRowLoopPane;
25212
27995
  exports.TextBoxObject = TextBoxObject;
27996
+ exports.TextBoxPane = TextBoxPane;
25213
27997
  exports.TextFormattingManager = TextFormattingManager;
25214
27998
  exports.TextLayout = TextLayout;
25215
27999
  exports.TextMeasurer = TextMeasurer;
25216
28000
  exports.TextPositionCalculator = TextPositionCalculator;
25217
28001
  exports.TextState = TextState;
25218
28002
  exports.VerticalRuler = VerticalRuler;
28003
+ exports.ViewSettingsPane = ViewSettingsPane;
25219
28004
  //# sourceMappingURL=pc-editor.js.map