pptx-react-viewer 2.17.0 → 2.19.0

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 (132) hide show
  1. package/CHANGELOG.md +703 -0
  2. package/README.md +14 -14
  3. package/dist/{AiChatPanel-HCHCVQN2.js → AiChatPanel-2GBJETOY.js} +24 -24
  4. package/dist/AiChatPanel-2GBJETOY.js.br +0 -0
  5. package/dist/AiChatPanel-2GBJETOY.js.gz +0 -0
  6. package/dist/{AiChatPanel-RZ4A7P4H.mjs → AiChatPanel-FZUKTQPQ.mjs} +4 -4
  7. package/dist/AiChatPanel-FZUKTQPQ.mjs.br +0 -0
  8. package/dist/AiChatPanel-FZUKTQPQ.mjs.gz +0 -0
  9. package/dist/{Model3DScene-LK7LSQNH.js → Model3DScene-JHTI2AQ6.js} +2 -2
  10. package/dist/Model3DScene-JHTI2AQ6.js.br +0 -0
  11. package/dist/Model3DScene-JHTI2AQ6.js.gz +0 -0
  12. package/dist/{Model3DScene-I7FR74AD.mjs → Model3DScene-WRRQSYAZ.mjs} +1 -1
  13. package/dist/Model3DScene-WRRQSYAZ.mjs.br +0 -0
  14. package/dist/Model3DScene-WRRQSYAZ.mjs.gz +0 -0
  15. package/dist/{PowerPointViewer-Du7u4INi.d.ts → PowerPointViewer-DVFkeluD.d.ts} +2 -2
  16. package/dist/PowerPointViewer-DVFkeluD.d.ts.map +1 -0
  17. package/dist/{audience-content-store-BvqKy7Hf.d.ts → audience-content-store-ChVOSdBh.d.ts} +2 -2
  18. package/dist/{audience-content-store-BvqKy7Hf.d.ts.map → audience-content-store-ChVOSdBh.d.ts.map} +1 -1
  19. package/dist/{chunk-7JPLVECA.js → chunk-7ARD2J6Y.js} +10502 -7059
  20. package/dist/chunk-7ARD2J6Y.js.br +0 -0
  21. package/dist/chunk-7ARD2J6Y.js.gz +0 -0
  22. package/dist/{chunk-ZA5JONCW.mjs → chunk-BO373PYH.mjs} +257 -184
  23. package/dist/chunk-BO373PYH.mjs.br +0 -0
  24. package/dist/chunk-BO373PYH.mjs.gz +0 -0
  25. package/dist/{chunk-TCGXGD6E.js → chunk-IRLOXBO7.js} +608 -535
  26. package/dist/chunk-IRLOXBO7.js.br +0 -0
  27. package/dist/chunk-IRLOXBO7.js.gz +0 -0
  28. package/dist/{chunk-AONFHTTJ.js → chunk-MVKRI3CZ.js} +36 -4
  29. package/dist/chunk-MVKRI3CZ.js.br +0 -0
  30. package/dist/chunk-MVKRI3CZ.js.gz +0 -0
  31. package/dist/{chunk-MQHC5TXI.mjs → chunk-MWEBCWVD.mjs} +38 -2347
  32. package/dist/chunk-MWEBCWVD.mjs.br +0 -0
  33. package/dist/chunk-MWEBCWVD.mjs.gz +0 -0
  34. package/dist/{chunk-NQJPPIVH.js → chunk-NFFV3RDS.js} +1632 -1201
  35. package/dist/chunk-NFFV3RDS.js.br +0 -0
  36. package/dist/chunk-NFFV3RDS.js.gz +0 -0
  37. package/dist/{chunk-5BXATG54.js → chunk-OVPJT4EQ.js} +182 -2490
  38. package/dist/chunk-OVPJT4EQ.js.br +0 -0
  39. package/dist/chunk-OVPJT4EQ.js.gz +0 -0
  40. package/dist/{chunk-ATOUVRRQ.js → chunk-RPJ2BIQ7.js} +3 -3
  41. package/dist/chunk-RPJ2BIQ7.js.br +0 -0
  42. package/dist/chunk-RPJ2BIQ7.js.gz +0 -0
  43. package/dist/{chunk-S64T7C4V.mjs → chunk-SQJBFMEX.mjs} +36 -4
  44. package/dist/chunk-SQJBFMEX.mjs.br +0 -0
  45. package/dist/chunk-SQJBFMEX.mjs.gz +0 -0
  46. package/dist/{chunk-K2QU73KB.mjs → chunk-XF3EOHY2.mjs} +10455 -7039
  47. package/dist/chunk-XF3EOHY2.mjs.br +0 -0
  48. package/dist/chunk-XF3EOHY2.mjs.gz +0 -0
  49. package/dist/{chunk-422DF424.mjs → chunk-YWJBD7KV.mjs} +654 -223
  50. package/dist/chunk-YWJBD7KV.mjs.br +0 -0
  51. package/dist/chunk-YWJBD7KV.mjs.gz +0 -0
  52. package/dist/{chunk-FXJOTEPO.mjs → chunk-ZMEYXAC6.mjs} +1 -1
  53. package/dist/chunk-ZMEYXAC6.mjs.br +0 -0
  54. package/dist/chunk-ZMEYXAC6.mjs.gz +0 -0
  55. package/dist/i18n.js +4 -4
  56. package/dist/i18n.js.br +0 -0
  57. package/dist/i18n.js.gz +0 -0
  58. package/dist/i18n.mjs +1 -1
  59. package/dist/i18n.mjs.br +0 -0
  60. package/dist/i18n.mjs.gz +0 -0
  61. package/dist/index.d.ts +535 -17
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +65 -41
  64. package/dist/index.js.br +0 -0
  65. package/dist/index.js.gz +0 -0
  66. package/dist/index.mjs +6 -6
  67. package/dist/index.mjs.br +0 -0
  68. package/dist/index.mjs.gz +0 -0
  69. package/dist/internals.d.ts +645 -47
  70. package/dist/internals.d.ts.map +1 -1
  71. package/dist/internals.js +79 -79
  72. package/dist/internals.js.br +0 -0
  73. package/dist/internals.js.gz +0 -0
  74. package/dist/internals.mjs +4 -4
  75. package/dist/internals.mjs.br +0 -0
  76. package/dist/internals.mjs.gz +0 -0
  77. package/dist/pptx-viewer.css +1 -1
  78. package/dist/pptx-viewer.css.br +0 -0
  79. package/dist/pptx-viewer.css.gz +0 -0
  80. package/dist/{types-DU35aAJK.d.ts → types-TCH6icpi.d.ts} +25 -31
  81. package/dist/types-TCH6icpi.d.ts.map +1 -0
  82. package/dist/{useViewerBuildingBlocks-C9-prKYB.d.ts → useViewerBuildingBlocks-BKb0qs1j.d.ts} +34 -5
  83. package/dist/useViewerBuildingBlocks-BKb0qs1j.d.ts.map +1 -0
  84. package/dist/viewer/index.d.ts +465 -40
  85. package/dist/viewer/index.js +27 -27
  86. package/dist/viewer/index.js.br +0 -0
  87. package/dist/viewer/index.js.gz +0 -0
  88. package/dist/viewer/index.mjs +6 -6
  89. package/dist/viewer/index.mjs.br +0 -0
  90. package/dist/viewer/index.mjs.gz +0 -0
  91. package/package.json +2 -2
  92. package/dist/AiChatPanel-HCHCVQN2.js.br +0 -0
  93. package/dist/AiChatPanel-HCHCVQN2.js.gz +0 -0
  94. package/dist/AiChatPanel-RZ4A7P4H.mjs.br +0 -0
  95. package/dist/AiChatPanel-RZ4A7P4H.mjs.gz +0 -0
  96. package/dist/Model3DScene-I7FR74AD.mjs.br +0 -0
  97. package/dist/Model3DScene-I7FR74AD.mjs.gz +0 -0
  98. package/dist/Model3DScene-LK7LSQNH.js.br +0 -0
  99. package/dist/Model3DScene-LK7LSQNH.js.gz +0 -0
  100. package/dist/PowerPointViewer-Du7u4INi.d.ts.map +0 -1
  101. package/dist/SurfaceChart3DScene-DEQP5O3U.mjs +0 -76
  102. package/dist/SurfaceChart3DScene-DEQP5O3U.mjs.br +0 -0
  103. package/dist/SurfaceChart3DScene-DEQP5O3U.mjs.gz +0 -0
  104. package/dist/SurfaceChart3DScene-ODQY77SZ.js +0 -78
  105. package/dist/SurfaceChart3DScene-ODQY77SZ.js.br +0 -0
  106. package/dist/SurfaceChart3DScene-ODQY77SZ.js.gz +0 -0
  107. package/dist/chunk-422DF424.mjs.br +0 -0
  108. package/dist/chunk-422DF424.mjs.gz +0 -0
  109. package/dist/chunk-5BXATG54.js.br +0 -0
  110. package/dist/chunk-5BXATG54.js.gz +0 -0
  111. package/dist/chunk-7JPLVECA.js.br +0 -0
  112. package/dist/chunk-7JPLVECA.js.gz +0 -0
  113. package/dist/chunk-AONFHTTJ.js.br +0 -0
  114. package/dist/chunk-AONFHTTJ.js.gz +0 -0
  115. package/dist/chunk-ATOUVRRQ.js.br +0 -0
  116. package/dist/chunk-ATOUVRRQ.js.gz +0 -0
  117. package/dist/chunk-FXJOTEPO.mjs.br +0 -0
  118. package/dist/chunk-FXJOTEPO.mjs.gz +0 -0
  119. package/dist/chunk-K2QU73KB.mjs.br +0 -0
  120. package/dist/chunk-K2QU73KB.mjs.gz +0 -0
  121. package/dist/chunk-MQHC5TXI.mjs.br +0 -0
  122. package/dist/chunk-MQHC5TXI.mjs.gz +0 -0
  123. package/dist/chunk-NQJPPIVH.js.br +0 -0
  124. package/dist/chunk-NQJPPIVH.js.gz +0 -0
  125. package/dist/chunk-S64T7C4V.mjs.br +0 -0
  126. package/dist/chunk-S64T7C4V.mjs.gz +0 -0
  127. package/dist/chunk-TCGXGD6E.js.br +0 -0
  128. package/dist/chunk-TCGXGD6E.js.gz +0 -0
  129. package/dist/chunk-ZA5JONCW.mjs.br +0 -0
  130. package/dist/chunk-ZA5JONCW.mjs.gz +0 -0
  131. package/dist/types-DU35aAJK.d.ts.map +0 -1
  132. package/dist/useViewerBuildingBlocks-C9-prKYB.d.ts.map +0 -1
@@ -1,6 +1,6 @@
1
- import { cloneHistorySnapshot, MIN_ELEMENT_SIZE, ZOOM_TO_SELECTION_PADDING, DEFAULT_CANVAS_HEIGHT, DEFAULT_CANVAS_WIDTH, UNGROUPED_SECTION_ID, DEFAULT_TABLE_ROWS, DEFAULT_TABLE_COLUMNS, executeMediaCommand, buildPdfFromImageData, buildNotesPdf, EMU_PER_PX, GRID_SIZE, DEFAULT_TEXT_FONT_SIZE } from './chunk-MQHC5TXI.mjs';
2
- import { translationsEn } from './chunk-S64T7C4V.mjs';
3
- import { TOOLBAR_TABS, computeVirtualRange, DEFAULT_VIRTUAL_OVERSCAN, createPresenceProjector, BROADCAST_THROTTLE_MS, createSyncGate, clearLocalAwareness, validateRoomId, createDepartureChannel, resolveTransportForServerUrl, isMixedContentBlocked, CONNECTION_TIMEOUT_MS, registerCollaborationTeardown, sanitizeColor, cloneTemplateElementsBySlideId, cloneSlide, createWheelStepBuffer, createViewerZoomStore, clampZoomScale, mapEditingWheel, PresentationAnimationController, PRESENTATION_HASH, PRESENTATION_NONCE_KEY, PRESENTATION_CHANNEL_NAME, swapPresentationWindows, PRESENTATION_MESSAGE_ORIGIN, clearAudienceContent, buildPresentationAudienceUrl, resolveAudienceScreenPlacement, placeAudienceWindow, storeAudienceContent, isPresentationSessionMessage, endAudienceDisplay, createPresentationKeyBuffer, acceptsPresentationInput, resolveAutoAdvanceDelayMs, hasShowSlideAfter, nextShowSlideIndex, previousShowSlideIndex, createPresenterShowGuard, attachPresentationVisibilityPause, buildMorphTransitionPlan, morphOptionToMode, buildMorphAnimationRules, findInSlides, applyFindReplacements, createCollaborationLivePatcher, publishLiveInlineText, PptxHandler, collectMediaElements, collectImagePaths, partitionTemplateElements, buildInitialGuides, EncryptedFileError, saveAutosaveSnapshot, updateMasterViewElement, isTemplateElementId, hasShapeProperties, hasTextProperties, getInlineEditorSelection, applyStyleToSelectedSegments, setPendingSelectionRestore, applyCaseTransformToSegments, transformTextCase, masterViewElements, replaceMasterViewElements, addSection, renameSection, deleteSection, moveSectionUp, moveSectionDown, moveSlidesToSection, normalizeColorsForCapture, preprocessCssForCapture, downloadBlob, sanitizeDownloadFilename, slideProgressPercent, slideStatusLabel, EXPORT_ASSEMBLING_PERCENT, EXPORT_DONE_PERCENT, isExportAbortError, recordProgressPercent, applyThemeToData, THEME_PRESETS, themeColorSchemesEqual, comparePresentation, applyAcceptSlide, applyAcceptAllSlides, collectUsedFonts, detectTouchDevice, detectOrientation, isMobileViewport, MOBILE_BREAKPOINT, TABLET_BREAKPOINT, describeFontEmbedding, mayLeaveSlideShow, stopAllPersistentAudio, remapTextToSegments, guidePxToEmu, buildSaveSlides, embeddedFontSaveOptions, saveDeckWithPassword, shouldLoopContinuously, applyRehearsalTimings, copyFormatFromElement, applyFormatToElement, mapEditorKey, isEditorTextInputTarget, createViewerOptionsStore, readSlidesFromYDoc, reconcileSlidesInYDoc, LOCAL_SYNC_ORIGIN, observeYDocSlides, shouldAutoFollowBroadcaster, createTouchGestureRecognizer, MAX_ZOOM_SCALE, MIN_ZOOM_SCALE, isKeyboardOpen, scopeLayoutOptionsToSlide, openPptxFile, readBackstageRecentFile, isActionHidden, canInteractWithElement, filterInteractableIds, buildElementClipboardPayload, generatePackageReadme, exportDeckJson, groupElements, generateElementId, makeCloneId, ungroupElements, alignElements, distributeElements, bringForward, sendBackward, bringToFront, sendToBack, newTableElement, createDefaultChartElement, readViewportMetrics, computeKeyboardInset, mergeShapes, mapPresentationKey, lastShowSlideIndex, firstShowSlideIndex, mapPresentationWheel, buildPrintDocument, buildOutlineHtml, buildSlidesHtml, buildNotesHtml, buildHandoutsHtml, createBlankSlide, makeSlideId, buildSlideTemplateSlide, templateSchemeFromTheme, buildThemeColorMap, reResolveSlideColors, computeEntranceAnimationDelay, createPresentationSessionId, resolveTransitionDurationMs, runPresentationAction, createPresenterTimer, createInitialPresentationSnapshot, mergePresentationSnapshot, resetPresenterTimer, presenterElapsed, stepPresenterZoom, togglePresenterTimer, canvasToJpegData, downloadDataUrl, pickSupportedMimeType, WEBM_MIME_CANDIDATES, fpsToFrameIntervalMs, segmentFrameCount, encodeGif, resolveShowSlideIndexes, groupSlidesBySection, masterViewPseudoSlide, hasCopyableFormat, createBackstagePresentation, buildFieldSubstitutionContext, cloneElementForPaste, computeScrollDelta, getPresetShapeClipPath, svgPathToPolygons, polygonsToSvgPath, rerouteConnectorsForMovedElements, applyReroutedConnectors, SvgExporter, buildPrintHtmlDocument, deleteTableColumn, rebuildTableStructureInRawXml, insertTableColumn, deleteTableRow, insertTableRow, updateCellTextStyleInRawXml, updateCellTextInRawXml, splitCell, updateMergeAttrsInRawXml, mergeCells, isPpactionUrl, parsePpactionUrl, safeOpenUrl, ACTION_BUTTON_PRESETS, elementActionToPptxAction, buildSmartArtPresetData, computeSnapToShape, publishLiveGeometry, getDraggedShapeAdjustmentValue, computeMarqueeHitIds, mergeAdditiveSelection, applyResize, snapBoxToGrid, applyMotionPathPreset } from './chunk-K2QU73KB.mjs';
1
+ import { cloneHistorySnapshot, MIN_ELEMENT_SIZE, ZOOM_TO_SELECTION_PADDING, DEFAULT_CANVAS_HEIGHT, DEFAULT_CANVAS_WIDTH, UNGROUPED_SECTION_ID, DEFAULT_SECTION_GROUP_ID, DEFAULT_TABLE_ROWS, DEFAULT_TABLE_COLUMNS, executeMediaCommand, buildPdfFromImageData, buildNotesPdf, EMU_PER_PX, GRID_SIZE, DEFAULT_TEXT_FONT_SIZE } from './chunk-MWEBCWVD.mjs';
2
+ import { translationsEn } from './chunk-SQJBFMEX.mjs';
3
+ import { TOOLBAR_TABS, computeVirtualRange, DEFAULT_VIRTUAL_OVERSCAN, createPresenceProjector, BROADCAST_THROTTLE_MS, createSyncGate, clearLocalAwareness, validateRoomId, createDepartureChannel, resolveTransportForServerUrl, isMixedContentBlocked, CONNECTION_TIMEOUT_MS, registerCollaborationTeardown, sanitizeColor, cloneTemplateElementsBySlideId, cloneSlide, createWheelStepBuffer, createViewerZoomStore, clampZoomScale, mapEditingWheel, PresentationAnimationController, PRESENTATION_HASH, PRESENTATION_NONCE_KEY, PRESENTATION_CHANNEL_NAME, swapPresentationWindows, PRESENTATION_MESSAGE_ORIGIN, clearAudienceContent, buildPresentationAudienceUrl, resolveAudienceScreenPlacement, placeAudienceWindow, storeAudienceContent, isPresentationSessionMessage, endAudienceDisplay, createPresentationKeyBuffer, acceptsPresentationInput, resolveAutoAdvanceDelayMs, hasShowSlideAfter, nextShowSlideIndex, previousShowSlideIndex, createPresenterShowGuard, attachPresentationVisibilityPause, buildMorphTransitionPlan, morphOptionToMode, buildMorphAnimationRules, findInSlides, applyFindReplacements, createCollaborationLivePatcher, publishLiveInlineText, PptxHandler, collectMediaElements, collectImagePaths, partitionTemplateElements, resolveAuthoredCustomShowId, buildInitialGuides, EncryptedFileError, resolveAutosaveIntervalMs, shouldWriteAutosaveSnapshot, saveAutosaveSnapshot, autosaveSnapshotMark, updateMasterViewElement, isTemplateElementId, hasShapeProperties, hasTextProperties, getInlineEditorSelection, applyStyleToSelectedSegments, setPendingSelectionRestore, applyCaseTransformToSegments, transformTextCase, masterViewElements, replaceMasterViewElements, addSection, renameSection, deleteSection, moveSectionUp, moveSectionDown, moveSlidesToSection, normalizeColorsForCapture, preprocessCssForCapture, downloadBlob, sanitizeDownloadFilename, slideProgressPercent, slideStatusLabel, EXPORT_ASSEMBLING_PERCENT, EXPORT_DONE_PERCENT, isExportAbortError, recordProgressPercent, applyThemeToData, THEME_PRESETS, themeColorSchemesEqual, comparePresentation, applyAcceptSlide, applyAcceptAllSlides, collectUsedFonts, detectTouchDevice, detectOrientation, isMobileViewport, MOBILE_BREAKPOINT, TABLET_BREAKPOINT, describeFontEmbedding, mayLeaveSlideShow, stopAllPersistentAudio, remapTextToSegments, guidePxToEmu, buildSaveSlides, embeddedFontSaveOptions, resolveSlideSizeSelection, saveDeckWithPassword, shouldProbeAutosaveRecovery, probeAutosaveRecovery, acceptAutosaveRecovery, discardAutosaveRecovery, shouldLoopContinuously, applyRehearsalTimings, copyFormatFromElement, applyFormatToElement, mapEditorKey, isEditorTextInputTarget, createViewerOptionsStore, readSlidesFromYDoc, reconcileSlidesInYDoc, LOCAL_SYNC_ORIGIN, observeYDocSlides, shouldAutoFollowBroadcaster, createTouchGestureRecognizer, MAX_ZOOM_SCALE, MIN_ZOOM_SCALE, isKeyboardOpen, scopeLayoutOptionsToSlide, openPptxFile, readBackstageRecentFile, isActionHidden, canInteractWithElement, filterInteractableIds, beginShapeAdjustment, buildElementClipboardPayload, savedPresentationFileName, generatePackageReadme, presentationBaseName, exportDeckJson, groupElements, generateElementId, makeCloneId, ungroupElements, alignElements, distributeElements, bringForward, sendBackward, bringToFront, sendToBack, newTableElement, createDefaultChartElement, readViewportMetrics, computeKeyboardInset, mergeShapes, mapPresentationKey, lastShowSlideIndex, firstShowSlideIndex, mapPresentationWheel, buildPrintDocument, buildOutlineHtml, buildSlidesHtml, buildNotesHtml, buildHandoutsHtml, createBlankSlide, makeSlideId, buildSlideTemplateSlide, templateSchemeFromTheme, buildThemeColorMap, reResolveSlideColors, computeEntranceAnimationDelay, createPresentationSessionId, resolveTransitionDurationMs, runPresentationAction, createPresenterTimer, createInitialPresentationSnapshot, mergePresentationSnapshot, resetPresenterTimer, presenterElapsed, stepPresenterZoom, togglePresenterTimer, canvasToJpegData, downloadDataUrl, pickSupportedMimeType, WEBM_MIME_CANDIDATES, fpsToFrameIntervalMs, segmentFrameCount, encodeGif, resolveShowSlideIndexes, groupSlidesBySection, masterViewPseudoSlide, hasCopyableFormat, createBackstagePresentation, buildFieldSubstitutionContext, cloneElementForPaste, computeScrollDelta, getPresetShapeClipPath, svgPathToPolygons, polygonsToSvgPath, rerouteConnectorsForMovedElements, applyReroutedConnectors, SvgExporter, buildPrintHtmlDocument, deleteTableColumn, rebuildTableStructureInRawXml, insertTableColumn, deleteTableRow, insertTableRow, updateCellTextStyleInRawXml, updateCellTextInRawXml, splitCell, updateMergeAttrsInRawXml, mergeCells, isPpactionUrl, parsePpactionUrl, safeOpenUrl, ACTION_BUTTON_PRESETS, elementActionToPptxAction, buildSmartArtPresetData, computeSnapToShape, publishLiveGeometry, getDraggedShapeAdjustments, computeMarqueeHitIds, mergeAdditiveSelection, applyResize, snapBoxToGrid, applyMotionPathPreset } from './chunk-XF3EOHY2.mjs';
4
4
  import { useRef, useState, useEffect, useCallback, useMemo, useLayoutEffect, useSyncExternalStore, useImperativeHandle } from 'react';
5
5
  import JSZip from 'jszip';
6
6
  import { useTranslation } from 'react-i18next';
@@ -473,6 +473,14 @@ function useSheetDismissDrag(onClose, threshold = 120) {
473
473
  };
474
474
  }
475
475
  var DEFAULT_MAX_HISTORY_ENTRIES = 120;
476
+ function serializeDocument(snapshot) {
477
+ return JSON.stringify({
478
+ width: snapshot.width,
479
+ height: snapshot.height,
480
+ slides: snapshot.slides,
481
+ templateElementsBySlideId: snapshot.templateElementsBySlideId
482
+ });
483
+ }
476
484
  function useEditorHistory(input) {
477
485
  const {
478
486
  slides,
@@ -575,7 +583,7 @@ function useEditorHistory(input) {
575
583
  isApplyingHistoryRef.current = true;
576
584
  const nextSnapshot = cloneHistorySnapshot(previousSnapshot);
577
585
  lastHistorySnapshotRef.current = cloneHistorySnapshot(nextSnapshot);
578
- lastHistorySerializedRef.current = JSON.stringify(nextSnapshot);
586
+ lastHistorySerializedRef.current = serializeDocument(nextSnapshot);
579
587
  applyHistorySnapshot(nextSnapshot);
580
588
  updateHistoryAvailability();
581
589
  unlockHistoryTracking();
@@ -597,7 +605,7 @@ function useEditorHistory(input) {
597
605
  isApplyingHistoryRef.current = true;
598
606
  const targetSnapshot = cloneHistorySnapshot(nextSnapshot);
599
607
  lastHistorySnapshotRef.current = cloneHistorySnapshot(targetSnapshot);
600
- lastHistorySerializedRef.current = JSON.stringify(targetSnapshot);
608
+ lastHistorySerializedRef.current = serializeDocument(targetSnapshot);
601
609
  applyHistorySnapshot(targetSnapshot);
602
610
  updateHistoryAvailability();
603
611
  unlockHistoryTracking();
@@ -616,7 +624,7 @@ function useEditorHistory(input) {
616
624
  if (initialSnapshot) {
617
625
  const clonedInitial = cloneHistorySnapshot(initialSnapshot);
618
626
  lastHistorySnapshotRef.current = clonedInitial;
619
- lastHistorySerializedRef.current = JSON.stringify(clonedInitial);
627
+ lastHistorySerializedRef.current = serializeDocument(clonedInitial);
620
628
  } else {
621
629
  lastHistorySnapshotRef.current = null;
622
630
  lastHistorySerializedRef.current = "";
@@ -648,8 +656,15 @@ function useEditorHistory(input) {
648
656
  return;
649
657
  }
650
658
  const snapshot = buildHistorySnapshot();
651
- const serialized = JSON.stringify(snapshot);
659
+ const serialized = serializeDocument(snapshot);
652
660
  if (serialized === lastHistorySerializedRef.current) {
661
+ const previous = lastHistorySnapshotRef.current;
662
+ if (previous && previous.activeSlideIndex !== snapshot.activeSlideIndex) {
663
+ lastHistorySnapshotRef.current = {
664
+ ...previous,
665
+ activeSlideIndex: snapshot.activeSlideIndex
666
+ };
667
+ }
653
668
  lastCheapHashRef.current = cheapHash;
654
669
  return;
655
670
  }
@@ -1676,6 +1691,7 @@ function usePresentationKeyboard(input) {
1676
1691
  onToggleInkMarkup,
1677
1692
  onToggleToolbar,
1678
1693
  onShowAllSlides,
1694
+ onToggleSubtitles,
1679
1695
  onToggleBlackScreen,
1680
1696
  onToggleWhiteScreen,
1681
1697
  rehearsing,
@@ -1741,6 +1757,9 @@ function usePresentationKeyboard(input) {
1741
1757
  case "showAllSlides":
1742
1758
  onShowAllSlides?.();
1743
1759
  return;
1760
+ case "toggleSubtitles":
1761
+ onToggleSubtitles?.();
1762
+ return;
1744
1763
  case "toggleBlackScreen":
1745
1764
  onToggleBlackScreen?.();
1746
1765
  return;
@@ -1780,6 +1799,7 @@ function usePresentationKeyboard(input) {
1780
1799
  onToggleInkMarkup,
1781
1800
  onToggleToolbar,
1782
1801
  onShowAllSlides,
1802
+ onToggleSubtitles,
1783
1803
  onToggleBlackScreen,
1784
1804
  onToggleWhiteScreen,
1785
1805
  rehearsing,
@@ -2220,6 +2240,7 @@ function usePresentationMode(input) {
2220
2240
  onEraseAnnotations,
2221
2241
  onToggleInkMarkup,
2222
2242
  onToggleToolbar,
2243
+ onToggleSubtitles,
2223
2244
  onSaveRehearsalTimings,
2224
2245
  loopContinuously,
2225
2246
  showWithAnimation,
@@ -2427,6 +2448,7 @@ function usePresentationMode(input) {
2427
2448
  onToggleInkMarkup,
2428
2449
  onToggleToolbar,
2429
2450
  onShowAllSlides: openAllSlides,
2451
+ onToggleSubtitles,
2430
2452
  onToggleBlackScreen: () => presenterConsole.setBlackout(
2431
2453
  presenterConsole.snapshot.blackout === "black" ? "none" : "black"
2432
2454
  ),
@@ -2909,13 +2931,15 @@ function useFindReplace({
2909
2931
  }, [findResults, applyReplacements, performFind]);
2910
2932
  useEffect(() => {
2911
2933
  const handler = (event) => {
2912
- if ((event.metaKey || event.ctrlKey) && event.key === "f") {
2913
- if (mode !== "edit") {
2914
- return;
2915
- }
2916
- event.preventDefault();
2917
- setFindReplaceOpen((prev) => !prev);
2934
+ const { action } = mapEditorKey(event, {
2935
+ canEdit: mode === "edit",
2936
+ isTextInputTarget: isEditorTextInputTarget(event.target)
2937
+ });
2938
+ if (action !== "find") {
2939
+ return;
2918
2940
  }
2941
+ event.preventDefault();
2942
+ setFindReplaceOpen((prev) => !prev);
2919
2943
  };
2920
2944
  window.addEventListener("keydown", handler);
2921
2945
  return () => window.removeEventListener("keydown", handler);
@@ -3355,9 +3379,28 @@ function useDerivedElementState(input) {
3355
3379
  }
3356
3380
  return templateElementsBySlideId[activeSlide.id] ?? [];
3357
3381
  }, [activeSlide, templateElementsBySlideId]);
3382
+ const activeMaster = slideMasters[activeMasterIndex];
3383
+ const activeLayout = useMemo(
3384
+ () => resolveActiveLayout(activeMaster, activeLayoutIndex),
3385
+ [activeMaster, activeLayoutIndex]
3386
+ );
3387
+ const masterViewElements2 = useMemo(
3388
+ () => computeMasterViewElements(activeMaster, activeLayout),
3389
+ [activeMaster, activeLayout]
3390
+ );
3391
+ const notesMasterElements = useMemo(() => notesMaster?.elements ?? [], [notesMaster]);
3392
+ const handoutMasterElements = useMemo(() => handoutMaster?.elements ?? [], [handoutMaster]);
3358
3393
  const elementLookup = useMemo(
3359
- () => buildElementLookup(templateElements, activeSlide?.elements ?? []),
3360
- [activeSlide, templateElements]
3394
+ () => buildElementLookup(
3395
+ [
3396
+ ...templateElements,
3397
+ ...masterViewElements2,
3398
+ ...notesMasterElements,
3399
+ ...handoutMasterElements
3400
+ ],
3401
+ activeSlide?.elements ?? []
3402
+ ),
3403
+ [activeSlide, templateElements, masterViewElements2, notesMasterElements, handoutMasterElements]
3361
3404
  );
3362
3405
  const selectedElement = useMemo(() => {
3363
3406
  if (!selectedElementId) {
@@ -3374,17 +3417,6 @@ function useDerivedElementState(input) {
3374
3417
  () => effectiveSelectedIds.map((id) => elementLookup.get(id)).filter(Boolean),
3375
3418
  [effectiveSelectedIds, elementLookup]
3376
3419
  );
3377
- const activeMaster = slideMasters[activeMasterIndex];
3378
- const activeLayout = useMemo(
3379
- () => resolveActiveLayout(activeMaster, activeLayoutIndex),
3380
- [activeMaster, activeLayoutIndex]
3381
- );
3382
- const masterViewElements2 = useMemo(
3383
- () => computeMasterViewElements(activeMaster, activeLayout),
3384
- [activeMaster, activeLayout]
3385
- );
3386
- const notesMasterElements = useMemo(() => notesMaster?.elements ?? [], [notesMaster]);
3387
- const handoutMasterElements = useMemo(() => handoutMaster?.elements ?? [], [handoutMaster]);
3388
3420
  return {
3389
3421
  activeSlide,
3390
3422
  templateElements,
@@ -3423,6 +3455,7 @@ function useViewerCoreState(_input) {
3423
3455
  width: DEFAULT_CANVAS_WIDTH,
3424
3456
  height: DEFAULT_CANVAS_HEIGHT
3425
3457
  });
3458
+ const [slideSizeEmu, setSlideSizeEmu] = useState();
3426
3459
  const [activeSlideIndex, setActiveSlideIndex] = useState(0);
3427
3460
  const [selectedElementId, setSelectedElementId] = useState(null);
3428
3461
  const [selectedElementIds, setSelectedElementIds] = useState([]);
@@ -3521,6 +3554,8 @@ function useViewerCoreState(_input) {
3521
3554
  setTemplateElementsBySlideId,
3522
3555
  canvasSize,
3523
3556
  setCanvasSize,
3557
+ slideSizeEmu,
3558
+ setSlideSizeEmu,
3524
3559
  activeSlideIndex,
3525
3560
  setActiveSlideIndex,
3526
3561
  selectedElementId,
@@ -3725,6 +3760,7 @@ function useLoadContent({
3725
3760
  setTemplateElementsBySlideId,
3726
3761
  mediaDataUrls,
3727
3762
  setCanvasSize,
3763
+ setSlideSizeEmu,
3728
3764
  setHeaderFooter,
3729
3765
  setLayoutOptions,
3730
3766
  setSlideMasters,
@@ -3732,6 +3768,7 @@ function useLoadContent({
3732
3768
  setTableStyleMap,
3733
3769
  setThemeOptions,
3734
3770
  setCustomShows,
3771
+ setActiveCustomShowId,
3735
3772
  setSections,
3736
3773
  setPresentationProperties,
3737
3774
  setNotesMaster,
@@ -3891,6 +3928,13 @@ function useLoadContent({
3891
3928
  width: parsed.width ?? DEFAULT_CANVAS_WIDTH,
3892
3929
  height: parsed.height ?? DEFAULT_CANVAS_HEIGHT
3893
3930
  });
3931
+ setSlideSizeEmu(
3932
+ typeof parsed.widthEmu === "number" && typeof parsed.heightEmu === "number" && parsed.widthEmu > 0 && parsed.heightEmu > 0 ? {
3933
+ widthEmu: parsed.widthEmu,
3934
+ heightEmu: parsed.heightEmu,
3935
+ type: parsed.slideSizeType ?? ""
3936
+ } : void 0
3937
+ );
3894
3938
  setHeaderFooter(parsed.headerFooter ?? {});
3895
3939
  setLayoutOptions(parsed.layoutOptions ?? []);
3896
3940
  setSlideMasters(parsed.slideMasters ?? []);
@@ -3898,6 +3942,9 @@ function useLoadContent({
3898
3942
  setTableStyleMap(parsed.tableStyleMap);
3899
3943
  setThemeOptions(parsed.themeOptions ?? []);
3900
3944
  setCustomShows(parsed.customShows ?? []);
3945
+ setActiveCustomShowId(
3946
+ resolveAuthoredCustomShowId(parsed.presentationProperties, parsed.customShows) ?? null
3947
+ );
3901
3948
  setSections(parsed.sections ?? []);
3902
3949
  setPresentationProperties(parsed.presentationProperties ?? {});
3903
3950
  setNotesMaster(parsed.notesMaster);
@@ -3951,23 +3998,12 @@ function useLoadContent({
3951
3998
  }, [content]);
3952
3999
  return { handlerRef };
3953
4000
  }
3954
-
3955
- // src/viewer/hooks/useAutosave-helpers.ts
3956
- var DEFAULT_AUTOSAVE_INTERVAL_SECONDS = 120;
3957
- var MIN_AUTOSAVE_INTERVAL_SECONDS = 10;
3958
- function computeAutosaveIntervalMs(intervalSeconds) {
3959
- return Math.max(intervalSeconds, MIN_AUTOSAVE_INTERVAL_SECONDS) * 1e3;
3960
- }
3961
-
3962
- // src/viewer/hooks/useAutosave.ts
3963
4001
  function useAutosave(input) {
3964
- const {
3965
- isDirty,
3966
- filePath,
3967
- serializeSlides,
3968
- intervalSeconds = DEFAULT_AUTOSAVE_INTERVAL_SECONDS,
3969
- enabled = true
3970
- } = input;
4002
+ const { isDirty, filePath, serializeSlides, enabled = true, getChangeSources } = input;
4003
+ const intervalMs = resolveAutosaveIntervalMs({
4004
+ hostIntervalMs: input.intervalMs,
4005
+ optionsIntervalSeconds: input.intervalSeconds
4006
+ });
3971
4007
  const [autosaveStatus, setAutosaveStatus] = useState({
3972
4008
  state: "idle"
3973
4009
  });
@@ -3975,6 +4011,8 @@ function useAutosave(input) {
3975
4011
  const filePathRef = useRef(filePath);
3976
4012
  const serializeRef = useRef(serializeSlides);
3977
4013
  const isSavingRef = useRef(false);
4014
+ const changeSourcesRef = useRef(getChangeSources);
4015
+ const lastSnapshotRef = useRef(void 0);
3978
4016
  useEffect(() => {
3979
4017
  isDirtyRef.current = isDirty;
3980
4018
  }, [isDirty]);
@@ -3984,14 +4022,22 @@ function useAutosave(input) {
3984
4022
  useEffect(() => {
3985
4023
  serializeRef.current = serializeSlides;
3986
4024
  }, [serializeSlides]);
3987
- const doAutosave = useCallback(async () => {
3988
- if (!filePathRef.current) {
3989
- return;
3990
- }
3991
- if (!isDirtyRef.current) {
3992
- return;
3993
- }
3994
- if (isSavingRef.current) {
4025
+ useEffect(() => {
4026
+ changeSourcesRef.current = getChangeSources;
4027
+ }, [getChangeSources]);
4028
+ const doAutosave = useCallback(async (options) => {
4029
+ const path = filePathRef.current;
4030
+ if (!path) {
4031
+ return;
4032
+ }
4033
+ const sources = changeSourcesRef.current?.() ?? [];
4034
+ if (!shouldWriteAutosaveSnapshot({
4035
+ filePath: path,
4036
+ isDirty: isDirtyRef.current,
4037
+ saving: isSavingRef.current,
4038
+ sources: options?.polled === true ? sources : [],
4039
+ lastSnapshot: lastSnapshotRef.current
4040
+ })) {
3995
4041
  return;
3996
4042
  }
3997
4043
  isSavingRef.current = true;
@@ -4003,7 +4049,8 @@ function useAutosave(input) {
4003
4049
  isSavingRef.current = false;
4004
4050
  return;
4005
4051
  }
4006
- await saveAutosaveSnapshot(filePathRef.current, data);
4052
+ await saveAutosaveSnapshot(path, data);
4053
+ lastSnapshotRef.current = autosaveSnapshotMark(path, sources);
4007
4054
  setAutosaveStatus({ state: "saved", timestamp: Date.now() });
4008
4055
  } catch (err) {
4009
4056
  setAutosaveStatus({
@@ -4027,15 +4074,14 @@ function useAutosave(input) {
4027
4074
  return;
4028
4075
  }
4029
4076
  setAutosaveStatus((prev) => prev.state === "disabled" ? { state: "idle" } : prev);
4030
- const ms = computeAutosaveIntervalMs(intervalSeconds);
4031
4077
  const id = setInterval(() => {
4032
- void doAutosave();
4033
- }, ms);
4078
+ void doAutosave({ polled: true });
4079
+ }, intervalMs);
4034
4080
  return () => clearInterval(id);
4035
- }, [enabled, filePath, intervalSeconds, doAutosave]);
4081
+ }, [enabled, filePath, intervalMs, doAutosave]);
4036
4082
  return {
4037
4083
  autosaveStatus,
4038
- triggerAutosave: doAutosave
4084
+ triggerAutosave: () => doAutosave()
4039
4085
  };
4040
4086
  }
4041
4087
  function useElementOperations(input) {
@@ -4327,13 +4373,23 @@ function useSectionOperations(input) {
4327
4373
  },
4328
4374
  [sections, slides, setSlides, setSections, markDirty]
4329
4375
  );
4376
+ const toggleSectionCollapse = useCallback(
4377
+ (sectionId) => {
4378
+ setSections(
4379
+ (prev) => prev.map((sec) => sec.id === sectionId ? { ...sec, collapsed: !sec.collapsed } : sec)
4380
+ );
4381
+ markDirty();
4382
+ },
4383
+ [setSections, markDirty]
4384
+ );
4330
4385
  return {
4331
4386
  addSection: addSection2,
4332
4387
  renameSection: renameSection2,
4333
4388
  deleteSection: deleteSection2,
4334
4389
  moveSectionUp: moveSectionUp2,
4335
4390
  moveSectionDown: moveSectionDown2,
4336
- moveSlidesToSection: moveSlidesToSection2
4391
+ moveSlidesToSection: moveSlidesToSection2,
4392
+ toggleSectionCollapse
4337
4393
  };
4338
4394
  }
4339
4395
 
@@ -4551,13 +4607,14 @@ function processResizeMove(e, rs, editorScale, snapToGrid, gridSpacingPx, live)
4551
4607
  }
4552
4608
  function processAdjustmentMove(e, adj, editorScale, updateElementById) {
4553
4609
  const dx = (e.clientX - adj.startClientX) / editorScale;
4554
- const newValue = getDraggedShapeAdjustmentValue(adj, dx);
4555
- if (!adj.moved && Math.abs(dx) > 2) {
4610
+ const dy = (e.clientY - adj.startClientY) / editorScale;
4611
+ const adjustments = getDraggedShapeAdjustments(adj, dx, dy);
4612
+ if (!adj.moved && Math.hypot(dx, dy) > 2) {
4556
4613
  adj.moved = true;
4557
4614
  }
4558
4615
  if (adj.moved) {
4559
4616
  updateElementById(adj.elementId, {
4560
- shapeAdjustments: { [adj.key]: newValue }
4617
+ shapeAdjustments: adjustments
4561
4618
  });
4562
4619
  }
4563
4620
  }
@@ -5034,33 +5091,18 @@ function useCanvasInteractions(input) {
5034
5091
  const handleFormatText = (updates) => {
5035
5092
  ops.updateSelectedTextStyle(updates);
5036
5093
  };
5037
- const handleAdjustmentPointerDown = (elementId, e) => {
5094
+ const handleAdjustmentPointerDown = (elementId, e, descriptor) => {
5038
5095
  e.stopPropagation();
5039
5096
  const el = elementLookup.get(elementId);
5040
- if (!el || !("shapeType" in el) || !("shapeAdjustments" in el)) {
5041
- return;
5042
- }
5043
- if (!canInteractWithElement(el, "adjustHandle")) {
5097
+ if (!el || !canInteractWithElement(el, "adjustHandle")) {
5044
5098
  return;
5045
5099
  }
5046
- const adjEntries = Object.entries(
5047
- el.shapeAdjustments ?? {}
5100
+ shapeAdjustmentDragStateRef.current = beginShapeAdjustment(
5101
+ el,
5102
+ descriptor,
5103
+ e.clientX,
5104
+ e.clientY
5048
5105
  );
5049
- if (!adjEntries.length) {
5050
- return;
5051
- }
5052
- const [key, value] = adjEntries[0];
5053
- shapeAdjustmentDragStateRef.current = {
5054
- elementId,
5055
- key,
5056
- shapeType: el.shapeType ?? "rect",
5057
- startClientX: e.clientX,
5058
- startClientY: e.clientY,
5059
- startAdjustment: value,
5060
- startWidth: el.width,
5061
- startHeight: el.height,
5062
- moved: false
5063
- };
5064
5106
  };
5065
5107
  return {
5066
5108
  handleElementClick,
@@ -6975,12 +7017,14 @@ function useExportSaveAs(input) {
6975
7017
  appProperties,
6976
7018
  customProperties,
6977
7019
  tagCollections,
7020
+ slideMasters,
6978
7021
  notesMaster,
6979
7022
  handoutMaster,
6980
7023
  guides,
6981
7024
  activeSlideIndexForGuides,
6982
7025
  theme,
6983
7026
  canvasSize,
7027
+ slideSizeEmu,
6984
7028
  modalControls,
6985
7029
  password
6986
7030
  } = input;
@@ -7004,7 +7048,7 @@ function useExportSaveAs(input) {
7004
7048
  setExportProgress(10);
7005
7049
  setExportStatusMessage("Adding presentation...");
7006
7050
  const pptxData = await serializeSlides();
7007
- const pptxFilename = filePath ? filePath.replace(/\\/gu, "/").split("/").pop() ?? "presentation.pptx" : "presentation.pptx";
7051
+ const pptxFilename = savedPresentationFileName(filePath);
7008
7052
  if (pptxData) {
7009
7053
  pkgFolder.file(pptxFilename, pptxData);
7010
7054
  }
@@ -7020,8 +7064,7 @@ function useExportSaveAs(input) {
7020
7064
  const zipBlob = await zip.generateAsync({ type: "blob" });
7021
7065
  setExportProgress(95);
7022
7066
  setExportStatusMessage("Downloading...");
7023
- const baseName = pptxFilename.replace(/\.[^.]+$/u, "");
7024
- downloadBlob(zipBlob, `${baseName}-package.zip`);
7067
+ downloadBlob(zipBlob, `${presentationBaseName(pptxFilename)}-package.zip`);
7025
7068
  setExportProgress(100);
7026
7069
  } catch (err) {
7027
7070
  if (err.name !== "AbortError") {
@@ -7037,16 +7080,15 @@ function useExportSaveAs(input) {
7037
7080
  if (!handler) {
7038
7081
  return;
7039
7082
  }
7040
- const ext = format === "ppsx" ? "ppsx" : format === "pptm" ? "pptm" : "pptx";
7041
- const baseName = filePath ? filePath.replace(/\\/gu, "/").split("/").pop()?.replace(/\.[^.]+$/u, "") ?? "presentation" : "presentation";
7083
+ const downloadName = savedPresentationFileName(filePath, format);
7042
7084
  try {
7043
7085
  const data = await buildSaveAsData(handler, format);
7044
7086
  const blob = new Blob([data], {
7045
7087
  type: "application/vnd.openxmlformats-officedocument.presentationml.presentation"
7046
7088
  });
7047
- downloadBlob(blob, `${baseName}.${ext}`);
7089
+ downloadBlob(blob, downloadName);
7048
7090
  } catch (err) {
7049
- console.error(`[PowerPointViewer] Save as .${ext} failed:`, err);
7091
+ console.error(`[PowerPointViewer] Save as .${format} failed:`, err);
7050
7092
  }
7051
7093
  };
7052
7094
  const buildSaveAsData = async (handler, format) => {
@@ -7066,6 +7108,9 @@ function useExportSaveAs(input) {
7066
7108
  });
7067
7109
  const slidesToSave = buildSaveSlides(slidesWithGuides, templateElementsBySlideId);
7068
7110
  const saveOptions = {
7111
+ // Save As shares Save's slide-size decision; leaving it out here is how
7112
+ // a preset picked in the inspector reached one save path and not the other.
7113
+ slideSize: resolveSlideSizeSelection({ current: slideSizeEmu, canvas: canvasSize }).size,
7069
7114
  headerFooter,
7070
7115
  presentationProperties,
7071
7116
  customShows: customShows.length > 0 ? customShows : void 0,
@@ -7074,6 +7119,7 @@ function useExportSaveAs(input) {
7074
7119
  appProperties: appProperties ?? void 0,
7075
7120
  customProperties: customProperties.length > 0 ? customProperties : void 0,
7076
7121
  tags: tagCollections.length > 0 ? tagCollections : void 0,
7122
+ slideMasters,
7077
7123
  notesMaster,
7078
7124
  handoutMaster,
7079
7125
  outputFormat: format
@@ -7082,12 +7128,14 @@ function useExportSaveAs(input) {
7082
7128
  };
7083
7129
  const handleExportJson = () => {
7084
7130
  const sourceName = filePath ? filePath.replace(/\\/gu, "/").split("/").pop() : void 0;
7131
+ const slideSize = resolveSlideSizeSelection({ current: slideSizeEmu, canvas: canvasSize }).size;
7085
7132
  const data = {
7086
7133
  slides: buildSaveSlides(slides, templateElementsBySlideId),
7087
7134
  width: canvasSize.width,
7088
7135
  height: canvasSize.height,
7089
- widthEmu: guidePxToEmu(canvasSize.width),
7090
- heightEmu: guidePxToEmu(canvasSize.height),
7136
+ widthEmu: slideSize.widthEmu,
7137
+ heightEmu: slideSize.heightEmu,
7138
+ slideSizeType: slideSize.type === "" ? void 0 : slideSize.type,
7091
7139
  theme,
7092
7140
  headerFooter,
7093
7141
  presentationProperties,
@@ -7164,12 +7212,14 @@ function useExportHandlers(input) {
7164
7212
  appProperties,
7165
7213
  customProperties,
7166
7214
  tagCollections,
7215
+ slideMasters,
7167
7216
  notesMaster,
7168
7217
  handoutMaster,
7169
7218
  guides,
7170
7219
  activeSlideIndexForGuides,
7171
7220
  theme,
7172
7221
  canvasSize,
7222
+ slideSizeEmu,
7173
7223
  password
7174
7224
  } = input;
7175
7225
  const [exportModalOpen, setExportModalOpen] = useState(false);
@@ -7205,12 +7255,14 @@ function useExportHandlers(input) {
7205
7255
  appProperties,
7206
7256
  customProperties,
7207
7257
  tagCollections,
7258
+ slideMasters,
7208
7259
  notesMaster,
7209
7260
  handoutMaster,
7210
7261
  guides,
7211
7262
  activeSlideIndexForGuides,
7212
7263
  theme,
7213
7264
  canvasSize,
7265
+ slideSizeEmu,
7214
7266
  modalControls,
7215
7267
  password
7216
7268
  });
@@ -8380,7 +8432,7 @@ function computeVisibleSlideIndexes(slides, activeCustomShowId, customShows) {
8380
8432
  }
8381
8433
  function computeSlideSectionGroups(slides, sections) {
8382
8434
  return groupSlidesBySection(sections, slides).map((group) => ({
8383
- id: group.section?.id ?? (sections.length > 0 ? UNGROUPED_SECTION_ID : "default"),
8435
+ id: group.section?.id ?? (sections.length > 0 ? UNGROUPED_SECTION_ID : DEFAULT_SECTION_GROUP_ID),
8384
8436
  label: group.section?.name ?? (sections.length > 0 ? "Ungrouped Slides" : "Slides"),
8385
8437
  slideIndexes: group.slideIndexes,
8386
8438
  ...group.section?.color !== void 0 ? { color: group.section.color } : {},
@@ -8423,7 +8475,7 @@ function useDerivedSlideState(input) {
8423
8475
  if (group.id === UNGROUPED_SECTION_ID && group.label === "Ungrouped Slides") {
8424
8476
  return { ...group, label: t("pptx.slides.ungroupedSlides") };
8425
8477
  }
8426
- if (group.id === "default" && group.label === "Slides") {
8478
+ if (group.id === DEFAULT_SECTION_GROUP_ID && group.label === "Slides") {
8427
8479
  return { ...group, label: t("pptx.sections.slides") };
8428
8480
  }
8429
8481
  return group;
@@ -8577,6 +8629,8 @@ function useSerialize(input) {
8577
8629
  slides,
8578
8630
  templateElementsBySlideId,
8579
8631
  activeSlideIndex,
8632
+ canvasSize,
8633
+ slideSizeEmu,
8580
8634
  guides,
8581
8635
  headerFooter,
8582
8636
  presentationProperties,
@@ -8586,6 +8640,7 @@ function useSerialize(input) {
8586
8640
  appProperties,
8587
8641
  customProperties,
8588
8642
  tagCollections,
8643
+ slideMasters,
8589
8644
  notesMaster,
8590
8645
  handoutMaster,
8591
8646
  handlerRef,
@@ -8634,6 +8689,10 @@ function useSerialize(input) {
8634
8689
  });
8635
8690
  const slidesToSave = buildSaveSlides(slidesWithGuides, templateElementsBySlideId);
8636
8691
  const saveOptions = {
8692
+ // Without this the Slide Size card edited a viewer-only pixel value and
8693
+ // the saved `p:sldSz` never moved: passing `slideSize` is the only way a
8694
+ // slide-size edit reaches the file.
8695
+ slideSize: resolveSlideSizeSelection({ current: slideSizeEmu, canvas: canvasSize }).size,
8637
8696
  headerFooter,
8638
8697
  presentationProperties,
8639
8698
  customShows: customShows.length > 0 ? customShows : void 0,
@@ -8642,6 +8701,7 @@ function useSerialize(input) {
8642
8701
  appProperties,
8643
8702
  customProperties: customProperties.length > 0 ? customProperties : void 0,
8644
8703
  tags: tagCollections.length > 0 ? tagCollections : void 0,
8704
+ slideMasters,
8645
8705
  notesMaster,
8646
8706
  handoutMaster,
8647
8707
  ...embeddedFontSaveOptions(embedFonts)
@@ -8650,6 +8710,8 @@ function useSerialize(input) {
8650
8710
  }, [
8651
8711
  slides,
8652
8712
  templateElementsBySlideId,
8713
+ canvasSize,
8714
+ slideSizeEmu,
8653
8715
  headerFooter,
8654
8716
  presentationProperties,
8655
8717
  customShows,
@@ -8658,6 +8720,7 @@ function useSerialize(input) {
8658
8720
  appProperties,
8659
8721
  customProperties,
8660
8722
  tagCollections,
8723
+ slideMasters,
8661
8724
  notesMaster,
8662
8725
  handoutMaster,
8663
8726
  guides,
@@ -8670,102 +8733,52 @@ function useSerialize(input) {
8670
8733
  purpose
8671
8734
  ]);
8672
8735
  }
8673
-
8674
- // src/viewer/hooks/useRecoveryDetection-helpers.ts
8675
- function shouldCheckRecovery(input) {
8676
- const { alreadyChecked, filePath, loading, error, slideCount } = input;
8677
- if (alreadyChecked) {
8678
- return false;
8679
- }
8680
- if (!filePath) {
8681
- return false;
8682
- }
8683
- if (loading) {
8684
- return false;
8685
- }
8686
- if (error) {
8687
- return false;
8688
- }
8689
- if (slideCount === 0) {
8690
- return false;
8691
- }
8692
- return true;
8693
- }
8694
- var RECOVERY_WINDOW_MS = 24 * 60 * 60 * 1e3;
8695
- function isRecentRecovery(timestamp, now) {
8696
- return now - timestamp < RECOVERY_WINDOW_MS;
8697
- }
8698
- function hasRecentRecoveryVersion(versions, now) {
8699
- if (versions.length === 0) {
8700
- return false;
8701
- }
8702
- return isRecentRecovery(versions[0].timestamp, now);
8703
- }
8704
-
8705
- // src/viewer/hooks/useRecoveryDetection.ts
8706
- var DB_NAME = "pptx-viewer-autosave";
8707
- var DB_VERSION = 1;
8708
- var STORE_NAME = "recoveryVersions";
8709
- function openAutosaveDb() {
8710
- return new Promise((resolve, reject) => {
8711
- const req = indexedDB.open(DB_NAME, DB_VERSION);
8712
- req.onupgradeneeded = () => {
8713
- const db = req.result;
8714
- if (!db.objectStoreNames.contains(STORE_NAME)) {
8715
- db.createObjectStore(STORE_NAME, { keyPath: "key" });
8716
- }
8717
- };
8718
- req.onsuccess = () => resolve(req.result);
8719
- req.onerror = () => reject(req.error);
8720
- });
8721
- }
8722
- async function getRecoveryVersion(filePath) {
8723
- try {
8724
- const db = await openAutosaveDb();
8725
- return new Promise((resolve) => {
8726
- const tx = db.transaction(STORE_NAME, "readonly");
8727
- const store = tx.objectStore(STORE_NAME);
8728
- const req = store.get(filePath);
8729
- req.onsuccess = () => {
8730
- db.close();
8731
- resolve(req.result);
8732
- };
8733
- req.onerror = () => {
8734
- db.close();
8735
- resolve(void 0);
8736
- };
8737
- });
8738
- } catch {
8739
- return void 0;
8740
- }
8741
- }
8742
8736
  function useRecoveryDetection(input) {
8743
- const { filePath, loading, error, slideCount, openVersionHistory } = input;
8737
+ const { filePath, loading, error, slideCount, autosaveAllowed = true } = input;
8744
8738
  const recoveryCheckedRef = useRef(false);
8739
+ const recordRef = useRef(null);
8740
+ const [prompt, setPrompt] = useState(null);
8741
+ const onRestore = input.onRestore;
8742
+ const openVersionHistory = input.openVersionHistory;
8745
8743
  useEffect(() => {
8746
- if (!shouldCheckRecovery({
8744
+ if (!shouldProbeAutosaveRecovery({
8747
8745
  alreadyChecked: recoveryCheckedRef.current,
8748
8746
  filePath,
8749
8747
  loading,
8750
8748
  error,
8751
- slideCount
8749
+ slideCount,
8750
+ autosaveAllowed
8752
8751
  })) {
8753
8752
  return;
8754
8753
  }
8755
8754
  recoveryCheckedRef.current = true;
8756
- if (typeof indexedDB === "undefined") {
8757
- return;
8758
- }
8759
8755
  void (async () => {
8760
- try {
8761
- const version = await getRecoveryVersion(filePath);
8762
- if (version && hasRecentRecoveryVersion([version], Date.now())) {
8763
- openVersionHistory();
8764
- }
8765
- } catch {
8756
+ const offer = await probeAutosaveRecovery(filePath);
8757
+ if (!offer) {
8758
+ return;
8766
8759
  }
8760
+ recordRef.current = offer.record;
8761
+ setPrompt(offer.prompt);
8762
+ openVersionHistory?.();
8767
8763
  })();
8768
- }, [filePath, loading, error, slideCount, openVersionHistory]);
8764
+ }, [filePath, loading, error, slideCount, autosaveAllowed, openVersionHistory]);
8765
+ const restore = useCallback(() => {
8766
+ const record = recordRef.current;
8767
+ setPrompt(null);
8768
+ if (record && onRestore) {
8769
+ onRestore(acceptAutosaveRecovery(record));
8770
+ }
8771
+ recordRef.current = null;
8772
+ }, [onRestore]);
8773
+ const discard = useCallback(() => {
8774
+ const record = recordRef.current;
8775
+ setPrompt(null);
8776
+ recordRef.current = null;
8777
+ if (record) {
8778
+ void discardAutosaveRecovery(record);
8779
+ }
8780
+ }, []);
8781
+ return { prompt, restore, discard };
8769
8782
  }
8770
8783
  function usePresentationSetup(input) {
8771
8784
  const {
@@ -8783,7 +8796,8 @@ function usePresentationSetup(input) {
8783
8796
  history,
8784
8797
  endWithBlackSlide = true,
8785
8798
  promptKeepInkAnnotations = true,
8786
- popupToolbarEnabled = true
8799
+ popupToolbarEnabled = true,
8800
+ onToggleSubtitles
8787
8801
  } = input;
8788
8802
  const actionSoundHandlerRef = useRef(null);
8789
8803
  const [, setShowKeepAnnotationsDialog] = useState(false);
@@ -8862,6 +8876,7 @@ function usePresentationSetup(input) {
8862
8876
  onEraseAnnotations: () => annotations.clearAnnotations(),
8863
8877
  onToggleInkMarkup: () => annotations.setInkMarkupVisible(!annotations.inkMarkupVisible),
8864
8878
  onToggleToolbar: () => annotations.setToolbarVisible(!annotations.toolbarVisible),
8879
+ onToggleSubtitles,
8865
8880
  onSaveRehearsalTimings: (timings) => {
8866
8881
  setSlides((prev) => applyRehearsalTimings(prev, timings));
8867
8882
  history.markDirty();
@@ -9161,12 +9176,14 @@ function useIOHandlers(input) {
9161
9176
  appProperties: state.appProperties ?? null,
9162
9177
  customProperties: state.customProperties,
9163
9178
  tagCollections: state.tagCollections,
9179
+ slideMasters: state.slideMasters,
9164
9180
  notesMaster: state.notesMaster,
9165
9181
  handoutMaster: state.handoutMaster,
9166
9182
  guides: state.guides,
9167
9183
  activeSlideIndexForGuides: state.activeSlideIndex,
9168
9184
  theme: state.theme,
9169
9185
  canvasSize,
9186
+ slideSizeEmu: state.slideSizeEmu,
9170
9187
  password
9171
9188
  });
9172
9189
  const printHandlers = usePrintHandlers({
@@ -9372,7 +9389,7 @@ function useContentLifecycle(input) {
9372
9389
  content,
9373
9390
  filePath,
9374
9391
  autosaveEnabled = true,
9375
- autosaveIntervalSeconds,
9392
+ autosaveIntervalMs,
9376
9393
  embedFonts = true,
9377
9394
  slides,
9378
9395
  state,
@@ -9391,6 +9408,7 @@ function useContentLifecycle(input) {
9391
9408
  setTemplateElementsBySlideId: state.setTemplateElementsBySlideId,
9392
9409
  mediaDataUrls: state.mediaDataUrls,
9393
9410
  setCanvasSize: state.setCanvasSize,
9411
+ setSlideSizeEmu: state.setSlideSizeEmu,
9394
9412
  setHeaderFooter: state.setHeaderFooter,
9395
9413
  setLayoutOptions: state.setLayoutOptions,
9396
9414
  setSlideMasters: state.setSlideMasters,
@@ -9398,6 +9416,7 @@ function useContentLifecycle(input) {
9398
9416
  setTableStyleMap: state.setTableStyleMap,
9399
9417
  setThemeOptions: state.setThemeOptions,
9400
9418
  setCustomShows: state.setCustomShows,
9419
+ setActiveCustomShowId: state.setActiveCustomShowId,
9401
9420
  setSections: state.setSections,
9402
9421
  setPresentationProperties: state.setPresentationProperties,
9403
9422
  setNotesMaster: state.setNotesMaster,
@@ -9427,6 +9446,8 @@ function useContentLifecycle(input) {
9427
9446
  slides,
9428
9447
  templateElementsBySlideId: state.templateElementsBySlideId,
9429
9448
  activeSlideIndex: state.activeSlideIndex,
9449
+ canvasSize: state.canvasSize,
9450
+ slideSizeEmu: state.slideSizeEmu,
9430
9451
  guides: state.guides,
9431
9452
  headerFooter: state.headerFooter,
9432
9453
  presentationProperties: state.presentationProperties,
@@ -9436,6 +9457,7 @@ function useContentLifecycle(input) {
9436
9457
  appProperties: state.appProperties,
9437
9458
  customProperties: state.customProperties,
9438
9459
  tagCollections: state.tagCollections,
9460
+ slideMasters: state.slideMasters,
9439
9461
  notesMaster: state.notesMaster,
9440
9462
  handoutMaster: state.handoutMaster,
9441
9463
  handlerRef,
@@ -9451,7 +9473,31 @@ function useContentLifecycle(input) {
9451
9473
  filePath,
9452
9474
  serializeSlides: serializeForRecovery,
9453
9475
  enabled: autosaveEnabled,
9454
- ...autosaveIntervalSeconds === void 0 ? {} : { intervalSeconds: autosaveIntervalSeconds }
9476
+ ...autosaveIntervalMs === void 0 ? {} : { intervalMs: autosaveIntervalMs },
9477
+ // Everything `serializeForRecovery` reads that changes by REASSIGNMENT,
9478
+ // so a poll that finds all of them unchanged can skip re-serializing a
9479
+ // deck it has already snapshotted. The two refs are read by `.current`
9480
+ // because an inline edit in progress lives there and nowhere else yet.
9481
+ getChangeSources: () => [
9482
+ slides,
9483
+ state.templateElementsBySlideId,
9484
+ state.guides,
9485
+ state.headerFooter,
9486
+ state.presentationProperties,
9487
+ state.customShows,
9488
+ state.sections,
9489
+ state.coreProperties,
9490
+ state.appProperties,
9491
+ state.customProperties,
9492
+ state.tagCollections,
9493
+ state.notesMaster,
9494
+ state.handoutMaster,
9495
+ state.embeddedFonts,
9496
+ embedFonts,
9497
+ password,
9498
+ state.inlineEditingElementIdRef.current,
9499
+ state.inlineEditingTextRef.current
9500
+ ]
9455
9501
  });
9456
9502
  return { handlerRef, serializeSlides, serializeForRecovery, autosaveStatus };
9457
9503
  }
@@ -9462,7 +9508,14 @@ function useKeyboardShortcuts(input) {
9462
9508
  const current = inputRef.current;
9463
9509
  const { action, dx, dy } = mapEditorKey(e, {
9464
9510
  canEdit: current.canEdit,
9465
- isPresenting: current.mode !== "edit",
9511
+ // The Slide Master view is an editing surface, not a viewing one.
9512
+ // Gating on `mode !== 'edit'` alone made `mapEditorKey` return
9513
+ // NO_ACTION there, so Delete, the arrow-key nudges and the clipboard
9514
+ // keys were all inert over a master shape even though the write path
9515
+ // behind them routes to the owning part correctly. Svelte and
9516
+ // vanilla, which render the master into their ordinary editable
9517
+ // stage, never had the gap.
9518
+ isPresenting: current.mode !== "edit" && current.mode !== "master",
9466
9519
  hasSelection: current.hasSelection,
9467
9520
  isEditingText: Boolean(current.inlineEditingElementId || current.tableEditorState?.isEditing),
9468
9521
  isDrawing: current.activeTool !== "select",
@@ -9509,6 +9562,9 @@ function useKeyboardShortcuts(input) {
9509
9562
  case "toggleShortcuts":
9510
9563
  current.onToggleShortcuts?.();
9511
9564
  break;
9565
+ case "find":
9566
+ current.onFind?.();
9567
+ break;
9512
9568
  case "nudge":
9513
9569
  current.onNudge(dx ?? 0, dy ?? 0);
9514
9570
  break;
@@ -9617,7 +9673,8 @@ function useViewerIntegration(input) {
9617
9673
  content,
9618
9674
  filePath,
9619
9675
  autosaveEnabled,
9620
- autosaveIntervalSeconds,
9676
+ autosaveAllowed = true,
9677
+ autosaveIntervalMs,
9621
9678
  canEdit,
9622
9679
  mode,
9623
9680
  slides,
@@ -9673,7 +9730,7 @@ function useViewerIntegration(input) {
9673
9730
  content,
9674
9731
  filePath,
9675
9732
  autosaveEnabled,
9676
- autosaveIntervalSeconds,
9733
+ autosaveIntervalMs,
9677
9734
  // The File > Fonts toggle, finally reaching a save call.
9678
9735
  embedFonts: dialogs.embedFontsEnabled,
9679
9736
  slides,
@@ -9717,12 +9774,13 @@ function useViewerIntegration(input) {
9717
9774
  setMode: state.setMode,
9718
9775
  setSlides: state.setSlides
9719
9776
  });
9720
- useRecoveryDetection({
9777
+ const recovery = useRecoveryDetection({
9721
9778
  filePath,
9722
9779
  loading,
9723
9780
  error,
9724
9781
  slideCount: slides.length,
9725
- openVersionHistory: () => propertyHandlers.setIsVersionHistoryOpen(true)
9782
+ autosaveAllowed,
9783
+ onRestore: setContent
9726
9784
  });
9727
9785
  useImperativeHandle(
9728
9786
  ref,
@@ -9930,6 +9988,7 @@ function useViewerIntegration(input) {
9930
9988
  handleEnterPresenterView,
9931
9989
  handleEnterRehearsalMode,
9932
9990
  autosaveStatus,
9991
+ recovery,
9933
9992
  isEncryptedDialogOpen,
9934
9993
  setIsEncryptedDialogOpen,
9935
9994
  handlerRef,
@@ -10855,6 +10914,15 @@ function buildToolbarProps(input) {
10855
10914
  onToggleVersionHistory: () => propertyHandlers.setIsVersionHistoryOpen((p) => !p),
10856
10915
  onOpenPasswordProtection: () => dialogs.setIsPasswordDialogOpen(true),
10857
10916
  onOpenDocumentProperties: () => dialogs.setIsDocPropsDialogOpen(true),
10917
+ // Design > Slide Size: the size control is the inspector's SLIDE SIZE
10918
+ // card, which the deck (no-selection) panel renders. Drop the selection so
10919
+ // that panel is what the pane shows, then open it.
10920
+ onOpenSlideSize: () => {
10921
+ s.setSelectedElementId(null);
10922
+ s.setSelectedElementIds([]);
10923
+ s.setSidebarPanelMode("properties");
10924
+ s.setIsInspectorPaneOpen(true);
10925
+ },
10858
10926
  onOpenFontEmbedding: () => dialogs.setIsFontEmbeddingOpen(true),
10859
10927
  onOpenDigitalSignatures: () => dialogs.setIsDigitalSigDialogOpen(true),
10860
10928
  onEnterPresenterView,
@@ -10981,7 +11049,12 @@ function useViewerBuildingBlocksCore(input) {
10981
11049
  setMode: state.setMode,
10982
11050
  setActiveSlideIndex: state.setActiveSlideIndex,
10983
11051
  setSlides: state.setSlides,
10984
- history
11052
+ history,
11053
+ // PowerPoint's bare `J` during a show toggles live captions.
11054
+ onToggleSubtitles: () => state.setPresentationProperties((prev) => ({
11055
+ ...prev,
11056
+ showSubtitles: !prev.showSubtitles
11057
+ }))
10985
11058
  });
10986
11059
  return {
10987
11060
  state,