@files-preview-app/preview-file 1.3.7 → 1.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.js CHANGED
@@ -1057,6 +1057,10 @@ var FilePreviewViewer = class _FilePreviewViewer {
1057
1057
  * Preview a file in the given container element.
1058
1058
  */
1059
1059
  async preview(container, source, options = {}) {
1060
+ options = {
1061
+ ...options,
1062
+ fitMode: options.fitMode ?? "width"
1063
+ };
1060
1064
  this.currentOptions = options;
1061
1065
  this.abort();
1062
1066
  this.abortController = new AbortController();
@@ -1105,8 +1109,8 @@ var FilePreviewViewer = class _FilePreviewViewer {
1105
1109
  this.activeInstance = instance;
1106
1110
  instance.openInSeparateWindow = () => this.openInSeparateWindow();
1107
1111
  instance.toggleThumbnails = () => this.toggleThumbnails();
1108
- if (!instance.resetZoom && instance.fitToPage) {
1109
- instance.resetZoom = () => instance.fitToPage?.();
1112
+ if (!instance.resetZoom) {
1113
+ instance.resetZoom = () => instance.fitToWidth ? instance.fitToWidth() : instance.fitToPage?.();
1110
1114
  }
1111
1115
  this.hideLoading();
1112
1116
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
@@ -1503,7 +1507,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1503
1507
  this.wrapperEl?.classList.toggle("fp-fullscreen-active");
1504
1508
  }
1505
1509
  setTimeout(() => {
1506
- this.activeInstance?.fitToPage?.();
1510
+ this.triggerAutoFit();
1507
1511
  }, 120);
1508
1512
  }
1509
1513
  /**
@@ -1630,18 +1634,29 @@ var FilePreviewViewer = class _FilePreviewViewer {
1630
1634
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl, (isOpen) => {
1631
1635
  this.toolbar?.setActionActive("thumbnails", isOpen);
1632
1636
  setTimeout(() => {
1633
- this.activeInstance?.fitToPage?.();
1637
+ this.triggerAutoFit();
1634
1638
  }, 260);
1635
1639
  });
1636
1640
  this.setupKeyboardShortcuts();
1637
1641
  this.setupDragAndDrop(container, options);
1638
1642
  this.setupResizeAndFullscreenListeners();
1639
1643
  }
1644
+ triggerAutoFit() {
1645
+ if (!this.activeInstance) return;
1646
+ const mode = this.currentOptions?.fitMode ?? "width";
1647
+ if (mode === "width" && this.activeInstance.fitToWidth) {
1648
+ this.activeInstance.fitToWidth();
1649
+ } else if (this.activeInstance.fitToPage) {
1650
+ this.activeInstance.fitToPage();
1651
+ } else if (this.activeInstance.resetZoom) {
1652
+ this.activeInstance.resetZoom();
1653
+ }
1654
+ }
1640
1655
  setupResizeAndFullscreenListeners() {
1641
1656
  if (this.fullscreenHandler) return;
1642
1657
  this.fullscreenHandler = () => {
1643
1658
  setTimeout(() => {
1644
- this.activeInstance?.fitToPage?.();
1659
+ this.triggerAutoFit();
1645
1660
  }, 100);
1646
1661
  };
1647
1662
  document.addEventListener("fullscreenchange", this.fullscreenHandler);
@@ -1649,7 +1664,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1649
1664
  if (typeof ResizeObserver !== "undefined" && this.contentEl) {
1650
1665
  this.resizeObserver = new ResizeObserver(() => {
1651
1666
  if (this.activeInstance && (!this.activeInstance.getZoom || Math.abs((this.activeInstance.getZoom?.() ?? 1) - 1) < 0.05)) {
1652
- this.activeInstance.fitToPage?.();
1667
+ this.triggerAutoFit();
1653
1668
  }
1654
1669
  });
1655
1670
  this.resizeObserver.observe(this.contentEl);
@@ -1677,7 +1692,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1677
1692
  } else if (e.key === "-" || e.key === "_") {
1678
1693
  this.activeInstance.zoomOut?.();
1679
1694
  } else if (e.key === "0") {
1680
- this.activeInstance.fitToPage?.();
1695
+ this.resetZoom();
1681
1696
  } else if (e.key === "r" || e.key === "R") {
1682
1697
  this.activeInstance.rotateCW?.();
1683
1698
  } else if (e.key === "f" || e.key === "F") {
@@ -2300,7 +2315,7 @@ var PdfPlugin = class {
2300
2315
  renderPage(currentPage);
2301
2316
  },
2302
2317
  resetZoom: () => {
2303
- fitMode = "page";
2318
+ fitMode = ctx?.options?.fitMode || "width";
2304
2319
  zoomScale = 1;
2305
2320
  rotation = 0;
2306
2321
  container.scrollTop = 0;
@@ -2484,6 +2499,16 @@ var MediaPlugin = class {
2484
2499
  instance.resetZoom?.();
2485
2500
  }
2486
2501
  },
2502
+ {
2503
+ id: "fit-width",
2504
+ icon: "fit-width",
2505
+ label: "Fit to Width",
2506
+ type: "button",
2507
+ group: "zoom",
2508
+ execute: () => {
2509
+ instance.fitToWidth?.();
2510
+ }
2511
+ },
2487
2512
  {
2488
2513
  id: "rotate-cw",
2489
2514
  icon: "rotate-cw",
@@ -2580,9 +2605,30 @@ var MediaPlugin = class {
2580
2605
  const isVideo = mimeType.startsWith("video/") || VIDEO_EXTS.includes(ctx.metadata.extension || "");
2581
2606
  const isAudio = mimeType.startsWith("audio/") || AUDIO_EXTS.includes(ctx.metadata.extension || "");
2582
2607
  let url = "";
2608
+ let naturalW = 800;
2609
+ let naturalH = 600;
2583
2610
  if (ctx.metadata.extension === ".svg" || mimeType === "image/svg+xml") {
2584
2611
  const decoder = new TextDecoder("utf-8");
2585
2612
  const svgText = decoder.decode(ctx.buffer);
2613
+ try {
2614
+ const vbMatch = svgText.match(/viewBox=["']\s*([0-9.-]+)\s+([0-9.-]+)\s+([0-9.-]+)\s+([0-9.-]+)\s*["']/i);
2615
+ if (vbMatch) {
2616
+ const vw = parseFloat(vbMatch[3]);
2617
+ const vh = parseFloat(vbMatch[4]);
2618
+ if (vw > 0 && vh > 0) {
2619
+ naturalW = vw;
2620
+ naturalH = vh;
2621
+ }
2622
+ } else {
2623
+ const wMatch = svgText.match(/width=["']([0-9.]+)(?:px)?["']/i);
2624
+ const hMatch = svgText.match(/height=["']([0-9.]+)(?:px)?["']/i);
2625
+ if (wMatch && hMatch) {
2626
+ naturalW = parseFloat(wMatch[1]) || naturalW;
2627
+ naturalH = parseFloat(hMatch[1]) || naturalH;
2628
+ }
2629
+ }
2630
+ } catch {
2631
+ }
2586
2632
  const blob = new Blob([svgText], { type: "image/svg+xml" });
2587
2633
  url = URL.createObjectURL(blob);
2588
2634
  } else {
@@ -2630,7 +2676,7 @@ var MediaPlugin = class {
2630
2676
  scrollWrapper.style.width = "max-content";
2631
2677
  scrollWrapper.style.height = "max-content";
2632
2678
  scrollWrapper.style.display = "flex";
2633
- scrollWrapper.style.justifyContent = "center";
2679
+ scrollWrapper.style.flexDirection = "column";
2634
2680
  scrollWrapper.style.alignItems = "center";
2635
2681
  scrollWrapper.style.padding = "16px";
2636
2682
  scrollWrapper.style.boxSizing = "border-box";
@@ -2641,40 +2687,54 @@ var MediaPlugin = class {
2641
2687
  sizer.style.display = "flex";
2642
2688
  sizer.style.justifyContent = "center";
2643
2689
  sizer.style.alignItems = "center";
2690
+ sizer.style.margin = "auto 0";
2644
2691
  sizer.appendChild(element);
2645
2692
  scrollWrapper.appendChild(sizer);
2646
2693
  ctx.container.style.overflow = "auto";
2647
2694
  ctx.container.style.padding = "0";
2648
2695
  ctx.container.innerHTML = "";
2649
2696
  ctx.container.appendChild(scrollWrapper);
2650
- let naturalW = 800;
2651
- let naturalH = 600;
2652
- let baseW = 800;
2653
- let baseH = 600;
2697
+ let baseW = naturalW;
2698
+ let baseH = naturalH;
2699
+ let fitMode = ctx?.options?.fitMode || "width";
2700
+ let isUserZoomed = false;
2654
2701
  const updateBaseDimensions = () => {
2655
2702
  if (!isImage) return;
2656
2703
  const img = element;
2657
- naturalW = img.naturalWidth || naturalW;
2658
- naturalH = img.naturalHeight || naturalH;
2659
- const availW = Math.max(100, ctx.container.clientWidth - 48);
2660
- const availH = Math.max(100, ctx.container.clientHeight - 48);
2661
- const fitRatio = Math.min(1, availW / naturalW, availH / naturalH);
2704
+ if (img.naturalWidth > 0 && img.naturalHeight > 0) {
2705
+ naturalW = img.naturalWidth;
2706
+ naturalH = img.naturalHeight;
2707
+ }
2708
+ const availW = Math.max(100, ctx.container.clientWidth - 32);
2709
+ const availH = Math.max(100, ctx.container.clientHeight - 32);
2710
+ let fitRatio;
2711
+ if (fitMode === "page") {
2712
+ fitRatio = Math.min(availW / naturalW, availH / naturalH);
2713
+ } else {
2714
+ fitRatio = availW / naturalW;
2715
+ }
2662
2716
  baseW = Math.max(1, Math.round(naturalW * fitRatio));
2663
2717
  baseH = Math.max(1, Math.round(naturalH * fitRatio));
2664
2718
  };
2665
2719
  const applyTransform = () => {
2666
2720
  if (isImage) {
2667
- updateBaseDimensions();
2721
+ if (!isUserZoomed) {
2722
+ updateBaseDimensions();
2723
+ }
2724
+ const availH = Math.max(100, ctx.container.clientHeight - 32);
2668
2725
  const isRotated90 = rotation % 180 !== 0;
2669
2726
  const boxW = Math.round((isRotated90 ? baseH : baseW) * currentZoom);
2670
2727
  const boxH = Math.round((isRotated90 ? baseW : baseH) * currentZoom);
2671
2728
  sizer.style.width = `${boxW}px`;
2672
2729
  sizer.style.height = `${boxH}px`;
2673
- element.style.width = `${baseW}px`;
2674
- element.style.height = `${baseH}px`;
2730
+ sizer.style.margin = boxH < availH ? "auto 0" : "0";
2731
+ const imgW = isRotated90 ? boxH : boxW;
2732
+ const imgH = isRotated90 ? boxW : boxH;
2733
+ element.style.width = `${imgW}px`;
2734
+ element.style.height = `${imgH}px`;
2675
2735
  element.style.maxWidth = "none";
2676
2736
  element.style.maxHeight = "none";
2677
- element.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
2737
+ element.style.transform = rotation ? `rotate(${rotation}deg)` : "none";
2678
2738
  element.style.transformOrigin = "center center";
2679
2739
  element.style.flexShrink = "0";
2680
2740
  } else {
@@ -2694,7 +2754,8 @@ var MediaPlugin = class {
2694
2754
  }
2695
2755
  }
2696
2756
  const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
2697
- if (currentZoom === 1) {
2757
+ if (!isUserZoomed && currentZoom === 1) {
2758
+ updateBaseDimensions();
2698
2759
  applyTransform();
2699
2760
  }
2700
2761
  }) : null;
@@ -2709,28 +2770,49 @@ var MediaPlugin = class {
2709
2770
  return {
2710
2771
  destroy: cleanup,
2711
2772
  zoomIn: isImage ? () => {
2712
- currentZoom += 0.1;
2773
+ isUserZoomed = true;
2774
+ currentZoom = Math.min(5, Math.round((currentZoom + 0.15) * 100) / 100);
2713
2775
  applyTransform();
2714
2776
  } : void 0,
2715
2777
  zoomOut: isImage ? () => {
2716
- currentZoom = Math.max(0.1, currentZoom - 0.1);
2778
+ isUserZoomed = true;
2779
+ currentZoom = Math.max(0.1, Math.round((currentZoom - 0.15) * 100) / 100);
2717
2780
  applyTransform();
2718
2781
  } : void 0,
2719
2782
  getZoom: isImage ? () => currentZoom : void 0,
2720
2783
  setZoom: isImage ? (level) => {
2784
+ isUserZoomed = true;
2721
2785
  currentZoom = level;
2722
2786
  applyTransform();
2723
2787
  } : void 0,
2724
2788
  fitToPage: isImage ? () => {
2789
+ isUserZoomed = false;
2790
+ fitMode = "page";
2725
2791
  currentZoom = 1;
2726
2792
  rotation = 0;
2793
+ ctx.container.scrollTop = 0;
2794
+ ctx.container.scrollLeft = 0;
2795
+ updateBaseDimensions();
2796
+ applyTransform();
2797
+ } : void 0,
2798
+ fitToWidth: isImage ? () => {
2799
+ isUserZoomed = false;
2800
+ fitMode = "width";
2801
+ currentZoom = 1;
2802
+ rotation = 0;
2803
+ ctx.container.scrollTop = 0;
2804
+ ctx.container.scrollLeft = 0;
2805
+ updateBaseDimensions();
2727
2806
  applyTransform();
2728
2807
  } : void 0,
2729
2808
  resetZoom: isImage ? () => {
2809
+ isUserZoomed = false;
2810
+ fitMode = ctx?.options?.fitMode || "width";
2730
2811
  currentZoom = 1;
2731
2812
  rotation = 0;
2732
2813
  ctx.container.scrollTop = 0;
2733
2814
  ctx.container.scrollLeft = 0;
2815
+ updateBaseDimensions();
2734
2816
  applyTransform();
2735
2817
  } : void 0,
2736
2818
  rotateCW: isImage || isVideo ? () => {
@@ -2857,6 +2939,14 @@ var DocxPlugin = class {
2857
2939
  group: "zoom",
2858
2940
  execute: () => instance.resetZoom?.()
2859
2941
  },
2942
+ {
2943
+ id: "fit-width",
2944
+ icon: "fit-width",
2945
+ label: "Fit to Width",
2946
+ type: "button",
2947
+ group: "zoom",
2948
+ execute: () => instance.fitToWidth?.()
2949
+ },
2860
2950
  {
2861
2951
  id: "rotate-cw",
2862
2952
  icon: "rotate-cw",
@@ -3116,13 +3206,19 @@ var DocxPlugin = class {
3116
3206
  }
3117
3207
  scale = 1;
3118
3208
  let rotation = 0;
3119
- let fitMode = "page";
3209
+ let fitMode = ctx?.options?.fitMode || "width";
3120
3210
  let isUserZoomed = false;
3121
- const calculateFitScale = (_mode = fitMode) => {
3211
+ const calculateFitScale = (mode = fitMode) => {
3122
3212
  const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
3123
3213
  const elW = activeEl.offsetWidth || 816;
3214
+ const elH = activeEl.offsetHeight || 1056;
3124
3215
  const availW = Math.max(280, ctx.container.clientWidth - 32);
3216
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
3125
3217
  const sW = availW / elW;
3218
+ const sH = availH / elH;
3219
+ if (mode === "page") {
3220
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
3221
+ }
3126
3222
  return Math.max(0.35, Math.min(3, sW));
3127
3223
  };
3128
3224
  const applyTransform = () => {
@@ -3143,7 +3239,7 @@ var DocxPlugin = class {
3143
3239
  if (typeof ResizeObserver !== "undefined") {
3144
3240
  resizeObserver = new ResizeObserver(() => {
3145
3241
  if (!isUserZoomed) {
3146
- const newFit = calculateFitScale("page");
3242
+ const newFit = calculateFitScale(fitMode);
3147
3243
  if (Math.abs(scale - newFit) > 0.015) {
3148
3244
  scale = newFit;
3149
3245
  applyTransform();
@@ -3153,7 +3249,7 @@ var DocxPlugin = class {
3153
3249
  resizeObserver.observe(ctx.container);
3154
3250
  }
3155
3251
  setTimeout(() => {
3156
- scale = calculateFitScale("page");
3252
+ scale = calculateFitScale(fitMode);
3157
3253
  applyTransform();
3158
3254
  }, 40);
3159
3255
  const cleanup = () => {
@@ -3192,13 +3288,22 @@ var DocxPlugin = class {
3192
3288
  },
3193
3289
  fitToPage: () => {
3194
3290
  isUserZoomed = false;
3291
+ fitMode = "page";
3195
3292
  scale = calculateFitScale("page");
3196
3293
  rotation = 0;
3197
3294
  applyTransform();
3198
3295
  },
3296
+ fitToWidth: () => {
3297
+ isUserZoomed = false;
3298
+ fitMode = "width";
3299
+ scale = calculateFitScale("width");
3300
+ rotation = 0;
3301
+ applyTransform();
3302
+ },
3199
3303
  resetZoom: () => {
3200
3304
  isUserZoomed = false;
3201
- scale = calculateFitScale("page");
3305
+ fitMode = ctx?.options?.fitMode || "width";
3306
+ scale = calculateFitScale(fitMode);
3202
3307
  rotation = 0;
3203
3308
  ctx.container.scrollTop = 0;
3204
3309
  ctx.container.scrollLeft = 0;
@@ -3663,6 +3768,16 @@ var ExcelPlugin = class {
3663
3768
  instance.resetZoom?.();
3664
3769
  }
3665
3770
  },
3771
+ {
3772
+ id: "fit-width",
3773
+ icon: "fit-width",
3774
+ label: "Fit to Width",
3775
+ type: "button",
3776
+ group: "zoom",
3777
+ execute: () => {
3778
+ instance.fitToWidth?.();
3779
+ }
3780
+ },
3666
3781
  {
3667
3782
  id: "download",
3668
3783
  icon: "download",
@@ -3879,6 +3994,11 @@ var ExcelPlugin = class {
3879
3994
  scale = calculateFitScale();
3880
3995
  applyTransform();
3881
3996
  },
3997
+ fitToWidth: () => {
3998
+ isUserZoomed = false;
3999
+ scale = calculateFitScale();
4000
+ applyTransform();
4001
+ },
3882
4002
  resetZoom: () => {
3883
4003
  isUserZoomed = false;
3884
4004
  scale = calculateFitScale();
@@ -4026,6 +4146,16 @@ var CsvPlugin = class {
4026
4146
  instance.resetZoom?.();
4027
4147
  }
4028
4148
  },
4149
+ {
4150
+ id: "fit-width",
4151
+ icon: "fit-width",
4152
+ label: "Fit to Width",
4153
+ type: "button",
4154
+ group: "zoom",
4155
+ execute: () => {
4156
+ instance.fitToWidth?.();
4157
+ }
4158
+ },
4029
4159
  {
4030
4160
  id: "download",
4031
4161
  icon: "download",
@@ -4239,6 +4369,11 @@ var CsvPlugin = class {
4239
4369
  scale = calculateFitScale();
4240
4370
  applyScale();
4241
4371
  },
4372
+ fitToWidth: () => {
4373
+ isUserZoomed = false;
4374
+ scale = calculateFitScale();
4375
+ applyScale();
4376
+ },
4242
4377
  resetZoom: () => {
4243
4378
  isUserZoomed = false;
4244
4379
  scale = calculateFitScale();
@@ -4371,6 +4506,16 @@ var CodePlugin = class {
4371
4506
  instance.resetZoom?.();
4372
4507
  }
4373
4508
  },
4509
+ {
4510
+ id: "fit-width",
4511
+ icon: "fit-width",
4512
+ label: "Fit to Width",
4513
+ type: "button",
4514
+ group: "zoom",
4515
+ execute: () => {
4516
+ instance.fitToWidth?.();
4517
+ }
4518
+ },
4374
4519
  {
4375
4520
  id: "copy",
4376
4521
  icon: "copy",
@@ -4675,6 +4820,13 @@ var CodePlugin = class {
4675
4820
  zoomLevel = isTxt ? calculateTxtFit() : 1;
4676
4821
  updateTransform();
4677
4822
  },
4823
+ fitToWidth: () => {
4824
+ isUserZoomed = false;
4825
+ fontSize = 13;
4826
+ rotation = 0;
4827
+ zoomLevel = isTxt ? calculateTxtFit() : 1;
4828
+ updateTransform();
4829
+ },
4678
4830
  resetZoom: () => {
4679
4831
  isUserZoomed = false;
4680
4832
  fontSize = 13;
@@ -4759,6 +4911,14 @@ var ArchivePlugin = class {
4759
4911
  group: "zoom",
4760
4912
  execute: () => instance.resetZoom?.()
4761
4913
  },
4914
+ {
4915
+ id: "fit-width",
4916
+ icon: "fit-width",
4917
+ label: "Fit to Width",
4918
+ type: "button",
4919
+ group: "zoom",
4920
+ execute: () => instance.fitToWidth?.()
4921
+ },
4762
4922
  {
4763
4923
  id: "download",
4764
4924
  icon: "download",
@@ -4956,6 +5116,10 @@ var ArchivePlugin = class {
4956
5116
  scale = 1;
4957
5117
  applyTransform();
4958
5118
  },
5119
+ fitToWidth: () => {
5120
+ scale = 1;
5121
+ applyTransform();
5122
+ },
4959
5123
  resetZoom: () => {
4960
5124
  scale = 1;
4961
5125
  wrapper.scrollTop = 0;
@@ -5020,6 +5184,14 @@ var MarkdownPlugin = class {
5020
5184
  group: "zoom",
5021
5185
  execute: () => instance.resetZoom?.()
5022
5186
  },
5187
+ {
5188
+ id: "fit-width",
5189
+ icon: "fit-width",
5190
+ label: "Fit to Width",
5191
+ type: "button",
5192
+ group: "zoom",
5193
+ execute: () => instance.fitToWidth?.()
5194
+ },
5023
5195
  {
5024
5196
  id: "copy",
5025
5197
  icon: "copy",
@@ -5173,6 +5345,10 @@ var MarkdownPlugin = class {
5173
5345
  fontSize = 15;
5174
5346
  wrapper.style.fontSize = "15px";
5175
5347
  },
5348
+ fitToWidth: () => {
5349
+ fontSize = 15;
5350
+ wrapper.style.fontSize = "15px";
5351
+ },
5176
5352
  resetZoom: () => {
5177
5353
  fontSize = 15;
5178
5354
  wrapper.style.fontSize = "15px";
@@ -5280,6 +5456,14 @@ var PptxPlugin = class {
5280
5456
  group: "zoom",
5281
5457
  execute: () => instance.resetZoom?.()
5282
5458
  },
5459
+ {
5460
+ id: "fit-width",
5461
+ icon: "fit-width",
5462
+ label: "Fit to Width",
5463
+ type: "button",
5464
+ group: "zoom",
5465
+ execute: () => instance.fitToWidth?.()
5466
+ },
5283
5467
  {
5284
5468
  id: "rotate-cw",
5285
5469
  icon: "rotate-cw",
@@ -5363,12 +5547,17 @@ var PptxPlugin = class {
5363
5547
  ctx.container.innerHTML = "";
5364
5548
  ctx.container.style.overflow = "auto";
5365
5549
  ctx.container.appendChild(wrapper);
5366
- const calculateFitScale = () => {
5367
- const availW = Math.max(200, ctx.container.clientWidth - 48);
5368
- const availH = Math.max(200, ctx.container.clientHeight - 72);
5550
+ let fitMode = ctx?.options?.fitMode || "width";
5551
+ let isUserZoomed = false;
5552
+ const calculateFitScale = (mode = fitMode) => {
5553
+ const availW = Math.max(200, ctx.container.clientWidth - 32);
5554
+ const availH = Math.max(200, ctx.container.clientHeight - 32);
5369
5555
  const cW = canvas.offsetWidth || 1280;
5370
5556
  const cH = canvas.offsetHeight || 720;
5371
- return Math.min(1, Math.min(availW / cW, availH / cH));
5557
+ if (mode === "page") {
5558
+ return Math.min(2.5, Math.min(availW / cW, availH / cH));
5559
+ }
5560
+ return Math.min(2.5, availW / cW);
5372
5561
  };
5373
5562
  const applyTransform = () => {
5374
5563
  const cW = canvas.offsetWidth || 1280;
@@ -5387,14 +5576,24 @@ var PptxPlugin = class {
5387
5576
  try {
5388
5577
  await renderer.renderSlide(currentSlide - 1, canvas, 1280);
5389
5578
  ctx.emit("page-change", { page: currentSlide, totalPages: slideCount });
5390
- scale = calculateFitScale();
5579
+ if (!isUserZoomed) {
5580
+ scale = calculateFitScale(fitMode);
5581
+ }
5391
5582
  applyTransform();
5392
5583
  } catch (err) {
5393
5584
  console.error("[PptxPlugin] Failed to render slide:", err);
5394
5585
  }
5395
5586
  };
5396
5587
  await renderCurrentSlide();
5588
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5589
+ if (!isUserZoomed) {
5590
+ scale = calculateFitScale(fitMode);
5591
+ applyTransform();
5592
+ }
5593
+ }) : null;
5594
+ ro?.observe(ctx.container);
5397
5595
  const cleanup = () => {
5596
+ ro?.disconnect();
5398
5597
  renderer.destroy();
5399
5598
  wrapper.remove();
5400
5599
  ctx.container.innerHTML = "";
@@ -5411,28 +5610,44 @@ var PptxPlugin = class {
5411
5610
  getPageCount: () => slideCount,
5412
5611
  getCurrentPage: () => currentSlide,
5413
5612
  zoomIn: () => {
5613
+ isUserZoomed = true;
5414
5614
  scale += 0.15;
5415
5615
  applyTransform();
5416
5616
  },
5417
5617
  zoomOut: () => {
5618
+ isUserZoomed = true;
5418
5619
  scale = Math.max(0.2, scale - 0.15);
5419
5620
  applyTransform();
5420
5621
  },
5421
5622
  getZoom: () => scale,
5422
5623
  setZoom: (level) => {
5624
+ isUserZoomed = true;
5423
5625
  scale = level;
5424
5626
  applyTransform();
5425
5627
  },
5426
5628
  fitToPage: () => {
5427
- scale = calculateFitScale();
5629
+ isUserZoomed = false;
5630
+ fitMode = "page";
5631
+ scale = calculateFitScale("page");
5632
+ rotation = 0;
5633
+ applyTransform();
5634
+ },
5635
+ fitToWidth: () => {
5636
+ isUserZoomed = false;
5637
+ fitMode = "width";
5638
+ scale = calculateFitScale("width");
5428
5639
  rotation = 0;
5429
5640
  applyTransform();
5430
5641
  },
5431
5642
  resetZoom: () => {
5432
- scale = calculateFitScale();
5643
+ isUserZoomed = false;
5644
+ fitMode = ctx?.options?.fitMode || "width";
5645
+ scale = calculateFitScale(fitMode);
5433
5646
  rotation = 0;
5434
5647
  wrapper.scrollTop = 0;
5435
5648
  wrapper.scrollLeft = 0;
5649
+ ctx.container.scrollTop = 0;
5650
+ ctx.container.scrollLeft = 0;
5436
5651
  applyTransform();
5437
5652
  },
5438
5653
  rotateCW: () => {
@@ -5877,6 +6092,14 @@ var RtfPlugin = class {
5877
6092
  group: "zoom",
5878
6093
  execute: () => instance.resetZoom?.()
5879
6094
  },
6095
+ {
6096
+ id: "fit-width",
6097
+ icon: "fit-width",
6098
+ label: "Fit to Width",
6099
+ type: "button",
6100
+ group: "zoom",
6101
+ execute: () => instance.fitToWidth?.()
6102
+ },
5880
6103
  {
5881
6104
  id: "rotate-cw",
5882
6105
  icon: "rotate-cw",
@@ -6365,6 +6588,13 @@ var RtfPlugin = class {
6365
6588
  applyTransform();
6366
6589
  },
6367
6590
  fitToPage: () => {
6591
+ isUserZoomed = false;
6592
+ fitMode = "page";
6593
+ scale = calculateFitScale("page");
6594
+ rotation = 0;
6595
+ applyTransform();
6596
+ },
6597
+ fitToWidth: () => {
6368
6598
  isUserZoomed = false;
6369
6599
  fitMode = "width";
6370
6600
  scale = calculateFitScale("width");
@@ -6373,8 +6603,8 @@ var RtfPlugin = class {
6373
6603
  },
6374
6604
  resetZoom: () => {
6375
6605
  isUserZoomed = false;
6376
- fitMode = "width";
6377
- scale = calculateFitScale("width");
6606
+ fitMode = ctx?.options?.fitMode || "width";
6607
+ scale = calculateFitScale(fitMode);
6378
6608
  rotation = 0;
6379
6609
  ctx.container.scrollTop = 0;
6380
6610
  ctx.container.scrollLeft = 0;
@@ -6455,6 +6685,14 @@ var HtmlPreviewPlugin = class {
6455
6685
  group: "zoom",
6456
6686
  execute: () => instance.resetZoom?.()
6457
6687
  },
6688
+ {
6689
+ id: "fit-width",
6690
+ icon: "fit-width",
6691
+ label: "Fit to Width",
6692
+ type: "button",
6693
+ group: "zoom",
6694
+ execute: () => instance.fitToWidth?.()
6695
+ },
6458
6696
  {
6459
6697
  id: "copy",
6460
6698
  icon: "copy",
@@ -6548,6 +6786,10 @@ var HtmlPreviewPlugin = class {
6548
6786
  scale = 1;
6549
6787
  applyTransform();
6550
6788
  },
6789
+ fitToWidth: () => {
6790
+ scale = 1;
6791
+ applyTransform();
6792
+ },
6551
6793
  resetZoom: () => {
6552
6794
  scale = 1;
6553
6795
  ctx.container.scrollTop = 0;
@@ -6662,6 +6904,14 @@ var OpenDocumentPlugin = class {
6662
6904
  group: "zoom",
6663
6905
  execute: () => instance.resetZoom?.()
6664
6906
  },
6907
+ {
6908
+ id: "fit-width",
6909
+ icon: "fit-width",
6910
+ label: "Fit to Width",
6911
+ type: "button",
6912
+ group: "zoom",
6913
+ execute: () => instance.fitToWidth?.()
6914
+ },
6665
6915
  {
6666
6916
  id: "rotate-cw",
6667
6917
  icon: "rotate-cw",
@@ -6778,7 +7028,10 @@ var OpenDocumentPlugin = class {
6778
7028
  const sW = availW / elW;
6779
7029
  const sH = availH / (isPresentation ? 540 : singlePageH);
6780
7030
  if (isPresentation) {
6781
- return Math.min(2.5, Math.min(sW, sH));
7031
+ if (mode === "page") {
7032
+ return Math.min(2.5, Math.min(sW, sH));
7033
+ }
7034
+ return Math.min(2.5, sW);
6782
7035
  }
6783
7036
  if (mode === "page") {
6784
7037
  return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
@@ -6947,6 +7200,13 @@ var OpenDocumentPlugin = class {
6947
7200
  applyTransform();
6948
7201
  },
6949
7202
  fitToPage: () => {
7203
+ isUserZoomed = false;
7204
+ fitMode = "page";
7205
+ scale = calculateFitScale("page");
7206
+ rotation = 0;
7207
+ applyTransform();
7208
+ },
7209
+ fitToWidth: () => {
6950
7210
  isUserZoomed = false;
6951
7211
  fitMode = "width";
6952
7212
  scale = calculateFitScale("width");
@@ -6955,8 +7215,8 @@ var OpenDocumentPlugin = class {
6955
7215
  },
6956
7216
  resetZoom: () => {
6957
7217
  isUserZoomed = false;
6958
- fitMode = "width";
6959
- scale = calculateFitScale("width");
7218
+ fitMode = ctx?.options?.fitMode || "width";
7219
+ scale = calculateFitScale(fitMode);
6960
7220
  rotation = 0;
6961
7221
  container.scrollTop = 0;
6962
7222
  container.scrollLeft = 0;
@@ -7313,6 +7573,14 @@ var DocPlugin = class {
7313
7573
  group: "zoom",
7314
7574
  execute: () => instance.resetZoom?.()
7315
7575
  },
7576
+ {
7577
+ id: "fit-width",
7578
+ icon: "fit-width",
7579
+ label: "Fit to Width",
7580
+ type: "button",
7581
+ group: "zoom",
7582
+ execute: () => instance.fitToWidth?.()
7583
+ },
7316
7584
  {
7317
7585
  id: "rotate-cw",
7318
7586
  icon: "rotate-cw",
@@ -7534,6 +7802,13 @@ var DocPlugin = class {
7534
7802
  applyTransform();
7535
7803
  },
7536
7804
  fitToPage: () => {
7805
+ isUserZoomed = false;
7806
+ fitMode = "page";
7807
+ scale = calculateFitScale("page");
7808
+ rotation = 0;
7809
+ applyTransform();
7810
+ },
7811
+ fitToWidth: () => {
7537
7812
  isUserZoomed = false;
7538
7813
  fitMode = "width";
7539
7814
  scale = calculateFitScale("width");
@@ -7542,8 +7817,8 @@ var DocPlugin = class {
7542
7817
  },
7543
7818
  resetZoom: () => {
7544
7819
  isUserZoomed = false;
7545
- fitMode = "width";
7546
- scale = calculateFitScale("width");
7820
+ fitMode = ctx?.options?.fitMode || "width";
7821
+ scale = calculateFitScale(fitMode);
7547
7822
  rotation = 0;
7548
7823
  container.scrollTop = 0;
7549
7824
  container.scrollLeft = 0;
@@ -8039,6 +8314,14 @@ var PptPlugin = class {
8039
8314
  group: "zoom",
8040
8315
  execute: () => instance.resetZoom?.()
8041
8316
  },
8317
+ {
8318
+ id: "fit-width",
8319
+ icon: "fit-width",
8320
+ label: "Fit to Width",
8321
+ type: "button",
8322
+ group: "zoom",
8323
+ execute: () => instance.fitToWidth?.()
8324
+ },
8042
8325
  {
8043
8326
  id: "rotate-cw",
8044
8327
  icon: "rotate-cw",
@@ -8113,10 +8396,15 @@ var PptPlugin = class {
8113
8396
  let currentSlide = 1;
8114
8397
  let slides = [];
8115
8398
  const createdBlobUrls = [];
8116
- const calculateFitScale = () => {
8117
- const availW = Math.max(200, container.clientWidth - 48);
8118
- const availH = Math.max(200, container.clientHeight - 72);
8119
- return Math.min(1, Math.min(availW / 960, availH / 540));
8399
+ let fitMode = ctx?.options?.fitMode || "width";
8400
+ let isUserZoomed = false;
8401
+ const calculateFitScale = (mode = fitMode) => {
8402
+ const availW = Math.max(200, container.clientWidth - 32);
8403
+ const availH = Math.max(200, container.clientHeight - 32);
8404
+ if (mode === "page") {
8405
+ return Math.min(2.5, Math.min(availW / 960, availH / 540));
8406
+ }
8407
+ return Math.min(2.5, availW / 960);
8120
8408
  };
8121
8409
  const applyTransform = () => {
8122
8410
  const cW = 960;
@@ -8131,8 +8419,15 @@ var PptPlugin = class {
8131
8419
  slideCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
8132
8420
  slideCard.style.transformOrigin = "center center";
8133
8421
  };
8422
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
8423
+ if (!isUserZoomed) {
8424
+ scale = calculateFitScale(fitMode);
8425
+ applyTransform();
8426
+ }
8427
+ }) : null;
8428
+ ro?.observe(container);
8134
8429
  setTimeout(() => {
8135
- scale = calculateFitScale();
8430
+ scale = calculateFitScale(fitMode);
8136
8431
  applyTransform();
8137
8432
  }, 60);
8138
8433
  try {
@@ -8225,6 +8520,7 @@ var PptPlugin = class {
8225
8520
  };
8226
8521
  renderSlide(1);
8227
8522
  const cleanup = () => {
8523
+ ro?.disconnect();
8228
8524
  for (const u of createdBlobUrls) {
8229
8525
  URL.revokeObjectURL(u);
8230
8526
  }
@@ -8235,28 +8531,44 @@ var PptPlugin = class {
8235
8531
  return {
8236
8532
  destroy: cleanup,
8237
8533
  zoomIn: () => {
8534
+ isUserZoomed = true;
8238
8535
  scale += 0.15;
8239
8536
  applyTransform();
8240
8537
  },
8241
8538
  zoomOut: () => {
8539
+ isUserZoomed = true;
8242
8540
  scale = Math.max(0.2, scale - 0.15);
8243
8541
  applyTransform();
8244
8542
  },
8245
8543
  getZoom: () => scale,
8246
8544
  setZoom: (level) => {
8545
+ isUserZoomed = true;
8247
8546
  scale = level;
8248
8547
  applyTransform();
8249
8548
  },
8250
8549
  fitToPage: () => {
8251
- scale = calculateFitScale();
8550
+ isUserZoomed = false;
8551
+ fitMode = "page";
8552
+ scale = calculateFitScale("page");
8553
+ rotation = 0;
8554
+ applyTransform();
8555
+ },
8556
+ fitToWidth: () => {
8557
+ isUserZoomed = false;
8558
+ fitMode = "width";
8559
+ scale = calculateFitScale("width");
8252
8560
  rotation = 0;
8253
8561
  applyTransform();
8254
8562
  },
8255
8563
  resetZoom: () => {
8256
- scale = calculateFitScale();
8564
+ isUserZoomed = false;
8565
+ fitMode = ctx?.options?.fitMode || "width";
8566
+ scale = calculateFitScale(fitMode);
8257
8567
  rotation = 0;
8258
8568
  container.scrollTop = 0;
8259
8569
  container.scrollLeft = 0;
8570
+ ctx.container.scrollTop = 0;
8571
+ ctx.container.scrollLeft = 0;
8260
8572
  applyTransform();
8261
8573
  },
8262
8574
  rotateCW: () => {