pptx-react-viewer 2.16.10 → 2.18.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 (136) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +14 -14
  3. package/dist/{AiChatPanel-XHKYPYPH.mjs → AiChatPanel-SSSBERWJ.mjs} +4 -4
  4. package/dist/AiChatPanel-SSSBERWJ.mjs.br +0 -0
  5. package/dist/AiChatPanel-SSSBERWJ.mjs.gz +0 -0
  6. package/dist/{AiChatPanel-EPMBL6EA.js → AiChatPanel-UN5DHPZY.js} +24 -24
  7. package/dist/AiChatPanel-UN5DHPZY.js.br +0 -0
  8. package/dist/AiChatPanel-UN5DHPZY.js.gz +0 -0
  9. package/dist/{Model3DScene-DUJOZA2B.js → Model3DScene-4RPYKC2I.js} +2 -2
  10. package/dist/Model3DScene-4RPYKC2I.js.br +0 -0
  11. package/dist/Model3DScene-4RPYKC2I.js.gz +0 -0
  12. package/dist/{Model3DScene-YMIAGRPG.mjs → Model3DScene-LYEESYAI.mjs} +1 -1
  13. package/dist/Model3DScene-LYEESYAI.mjs.br +0 -0
  14. package/dist/Model3DScene-LYEESYAI.mjs.gz +0 -0
  15. package/dist/{PowerPointViewer-Du7u4INi.d.ts → PowerPointViewer-Cf4YHjdF.d.ts} +2 -2
  16. package/dist/PowerPointViewer-Cf4YHjdF.d.ts.map +1 -0
  17. package/dist/{SurfaceChart3DScene-TNCHWQAK.js → SurfaceChart3DScene-ACPKCWLJ.js} +2 -2
  18. package/dist/SurfaceChart3DScene-ACPKCWLJ.js.br +0 -0
  19. package/dist/SurfaceChart3DScene-ACPKCWLJ.js.gz +0 -0
  20. package/dist/{SurfaceChart3DScene-Q47OEJXS.mjs → SurfaceChart3DScene-JR62SOYO.mjs} +1 -1
  21. package/dist/SurfaceChart3DScene-JR62SOYO.mjs.br +0 -0
  22. package/dist/SurfaceChart3DScene-JR62SOYO.mjs.gz +0 -0
  23. package/dist/{audience-content-store-BvqKy7Hf.d.ts → audience-content-store-CyS0oP6t.d.ts} +2 -2
  24. package/dist/{audience-content-store-BvqKy7Hf.d.ts.map → audience-content-store-CyS0oP6t.d.ts.map} +1 -1
  25. package/dist/{chunk-WK22Z7PK.js → chunk-4LWGAVAP.js} +526 -928
  26. package/dist/chunk-4LWGAVAP.js.br +0 -0
  27. package/dist/chunk-4LWGAVAP.js.gz +0 -0
  28. package/dist/{chunk-EL2YF26P.mjs → chunk-7XF6S4AM.mjs} +1 -1
  29. package/dist/chunk-7XF6S4AM.mjs.br +0 -0
  30. package/dist/chunk-7XF6S4AM.mjs.gz +0 -0
  31. package/dist/{chunk-FSVE6WUY.js → chunk-ASKYZ5ZY.js} +728 -523
  32. package/dist/chunk-ASKYZ5ZY.js.br +0 -0
  33. package/dist/chunk-ASKYZ5ZY.js.gz +0 -0
  34. package/dist/{chunk-VHME5BE6.mjs → chunk-BZOL6FXF.mjs} +326 -728
  35. package/dist/chunk-BZOL6FXF.mjs.br +0 -0
  36. package/dist/chunk-BZOL6FXF.mjs.gz +0 -0
  37. package/dist/{chunk-YWKUIUQX.mjs → chunk-GODW6RNR.mjs} +87648 -78804
  38. package/dist/chunk-GODW6RNR.mjs.br +0 -0
  39. package/dist/chunk-GODW6RNR.mjs.gz +0 -0
  40. package/dist/{chunk-ESC6IB2F.js → chunk-ICZDMFF2.js} +2147 -3399
  41. package/dist/chunk-ICZDMFF2.js.br +0 -0
  42. package/dist/chunk-ICZDMFF2.js.gz +0 -0
  43. package/dist/{chunk-PI5SFTDC.js → chunk-MAVLV56S.js} +3 -3
  44. package/dist/chunk-MAVLV56S.js.br +0 -0
  45. package/dist/chunk-MAVLV56S.js.gz +0 -0
  46. package/dist/{chunk-G3VAE7ZF.mjs → chunk-O7FTUUXP.mjs} +1212 -2464
  47. package/dist/chunk-O7FTUUXP.mjs.br +0 -0
  48. package/dist/chunk-O7FTUUXP.mjs.gz +0 -0
  49. package/dist/{chunk-J7EFC3GS.mjs → chunk-QUB5OSKY.mjs} +394 -189
  50. package/dist/chunk-QUB5OSKY.mjs.br +0 -0
  51. package/dist/chunk-QUB5OSKY.mjs.gz +0 -0
  52. package/dist/{chunk-DVZF4ARG.js → chunk-RTUGI4HQ.js} +49 -4
  53. package/dist/chunk-RTUGI4HQ.js.br +0 -0
  54. package/dist/chunk-RTUGI4HQ.js.gz +0 -0
  55. package/dist/{chunk-5IUEPQTK.mjs → chunk-SNL6LBRX.mjs} +49 -4
  56. package/dist/chunk-SNL6LBRX.mjs.br +0 -0
  57. package/dist/chunk-SNL6LBRX.mjs.gz +0 -0
  58. package/dist/{chunk-Y2KIB3QH.js → chunk-WVDM36ZM.js} +87730 -78843
  59. package/dist/chunk-WVDM36ZM.js.br +0 -0
  60. package/dist/chunk-WVDM36ZM.js.gz +0 -0
  61. package/dist/i18n.js +4 -4
  62. package/dist/i18n.js.br +0 -0
  63. package/dist/i18n.js.gz +0 -0
  64. package/dist/i18n.mjs +1 -1
  65. package/dist/i18n.mjs.br +0 -0
  66. package/dist/i18n.mjs.gz +0 -0
  67. package/dist/index.d.ts +702 -37
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +65 -41
  70. package/dist/index.js.br +0 -0
  71. package/dist/index.js.gz +0 -0
  72. package/dist/index.mjs +6 -6
  73. package/dist/index.mjs.br +0 -0
  74. package/dist/index.mjs.gz +0 -0
  75. package/dist/internals.d.ts +923 -54
  76. package/dist/internals.d.ts.map +1 -1
  77. package/dist/internals.js +79 -79
  78. package/dist/internals.js.br +0 -0
  79. package/dist/internals.js.gz +0 -0
  80. package/dist/internals.mjs +4 -4
  81. package/dist/internals.mjs.br +0 -0
  82. package/dist/internals.mjs.gz +0 -0
  83. package/dist/pptx-viewer.css +1 -1
  84. package/dist/pptx-viewer.css.br +0 -0
  85. package/dist/pptx-viewer.css.gz +0 -0
  86. package/dist/{types-DU35aAJK.d.ts → types-C_CTI-QE.d.ts} +3 -31
  87. package/dist/types-C_CTI-QE.d.ts.map +1 -0
  88. package/dist/{useViewerBuildingBlocks-Xddn6SFj.d.ts → useViewerBuildingBlocks-DItilDbP.d.ts} +47 -8
  89. package/dist/useViewerBuildingBlocks-DItilDbP.d.ts.map +1 -0
  90. package/dist/viewer/index.d.ts +593 -56
  91. package/dist/viewer/index.js +27 -27
  92. package/dist/viewer/index.js.br +0 -0
  93. package/dist/viewer/index.js.gz +0 -0
  94. package/dist/viewer/index.mjs +6 -6
  95. package/dist/viewer/index.mjs.br +0 -0
  96. package/dist/viewer/index.mjs.gz +0 -0
  97. package/package.json +2 -2
  98. package/dist/AiChatPanel-EPMBL6EA.js.br +0 -0
  99. package/dist/AiChatPanel-EPMBL6EA.js.gz +0 -0
  100. package/dist/AiChatPanel-XHKYPYPH.mjs.br +0 -0
  101. package/dist/AiChatPanel-XHKYPYPH.mjs.gz +0 -0
  102. package/dist/Model3DScene-DUJOZA2B.js.br +0 -0
  103. package/dist/Model3DScene-DUJOZA2B.js.gz +0 -0
  104. package/dist/Model3DScene-YMIAGRPG.mjs.br +0 -0
  105. package/dist/Model3DScene-YMIAGRPG.mjs.gz +0 -0
  106. package/dist/PowerPointViewer-Du7u4INi.d.ts.map +0 -1
  107. package/dist/SurfaceChart3DScene-Q47OEJXS.mjs.br +0 -0
  108. package/dist/SurfaceChart3DScene-Q47OEJXS.mjs.gz +0 -0
  109. package/dist/SurfaceChart3DScene-TNCHWQAK.js.br +0 -0
  110. package/dist/SurfaceChart3DScene-TNCHWQAK.js.gz +0 -0
  111. package/dist/chunk-5IUEPQTK.mjs.br +0 -0
  112. package/dist/chunk-5IUEPQTK.mjs.gz +0 -0
  113. package/dist/chunk-DVZF4ARG.js.br +0 -0
  114. package/dist/chunk-DVZF4ARG.js.gz +0 -0
  115. package/dist/chunk-EL2YF26P.mjs.br +0 -0
  116. package/dist/chunk-EL2YF26P.mjs.gz +0 -0
  117. package/dist/chunk-ESC6IB2F.js.br +0 -0
  118. package/dist/chunk-ESC6IB2F.js.gz +0 -0
  119. package/dist/chunk-FSVE6WUY.js.br +0 -0
  120. package/dist/chunk-FSVE6WUY.js.gz +0 -0
  121. package/dist/chunk-G3VAE7ZF.mjs.br +0 -0
  122. package/dist/chunk-G3VAE7ZF.mjs.gz +0 -0
  123. package/dist/chunk-J7EFC3GS.mjs.br +0 -0
  124. package/dist/chunk-J7EFC3GS.mjs.gz +0 -0
  125. package/dist/chunk-PI5SFTDC.js.br +0 -0
  126. package/dist/chunk-PI5SFTDC.js.gz +0 -0
  127. package/dist/chunk-VHME5BE6.mjs.br +0 -0
  128. package/dist/chunk-VHME5BE6.mjs.gz +0 -0
  129. package/dist/chunk-WK22Z7PK.js.br +0 -0
  130. package/dist/chunk-WK22Z7PK.js.gz +0 -0
  131. package/dist/chunk-Y2KIB3QH.js.br +0 -0
  132. package/dist/chunk-Y2KIB3QH.js.gz +0 -0
  133. package/dist/chunk-YWKUIUQX.mjs.br +0 -0
  134. package/dist/chunk-YWKUIUQX.mjs.gz +0 -0
  135. package/dist/types-DU35aAJK.d.ts.map +0 -1
  136. package/dist/useViewerBuildingBlocks-Xddn6SFj.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-VHME5BE6.mjs';
2
- import { translationsEn } from './chunk-5IUEPQTK.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, isTemplateElementId, hasShapeProperties, hasTextProperties, getInlineEditorSelection, applyStyleToSelectedSegments, setPendingSelectionRestore, applyCaseTransformToSegments, transformTextCase, 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, mayLeaveSlideShow, stopAllPersistentAudio, remapTextToSegments, guidePxToEmu, buildSaveSlides, shouldLoopContinuously, applyRehearsalTimings, copyFormatFromElement, applyFormatToElement, mapEditorKey, isEditorTextInputTarget, createViewerOptionsStore, readSlidesFromYDoc, reconcileSlidesInYDoc, LOCAL_SYNC_ORIGIN, observeYDocSlides, shouldAutoFollowBroadcaster, createTouchGestureRecognizer, MAX_ZOOM_SCALE, MIN_ZOOM_SCALE, isKeyboardOpen, openPptxFile, readBackstageRecentFile, isActionHidden, buildElementClipboardPayload, generatePackageReadme, exportDeckJson, groupElements, generateElementId, makeCloneId, 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, 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, computeMarqueeHitIds, mergeAdditiveSelection, applyResize, snapBoxToGrid, applyMotionPathPreset } from './chunk-YWKUIUQX.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-BZOL6FXF.mjs';
2
+ import { translationsEn } from './chunk-SNL6LBRX.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, 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, 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-GODW6RNR.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';
@@ -484,6 +484,7 @@ function useEditorHistory(input) {
484
484
  maxHistoryEntries = DEFAULT_MAX_HISTORY_ENTRIES,
485
485
  hasActivePointerInteraction,
486
486
  pointerCommitNonce,
487
+ onDirty,
487
488
  setSlides,
488
489
  setCanvasSize,
489
490
  setActiveSlideIndex,
@@ -556,8 +557,13 @@ function useEditorHistory(input) {
556
557
  isApplyingHistoryRef.current = false;
557
558
  }, 0);
558
559
  }, []);
560
+ const onDirtyRef = useRef(onDirty);
561
+ useEffect(() => {
562
+ onDirtyRef.current = onDirty;
563
+ }, [onDirty]);
559
564
  const markDirty = useCallback(() => {
560
565
  setEditCommitNonce((previous) => previous + 1);
566
+ onDirtyRef.current?.();
561
567
  }, []);
562
568
  const handleUndo = useCallback(() => {
563
569
  const previousSnapshot = historyPastRef.current.pop();
@@ -655,6 +661,7 @@ function useEditorHistory(input) {
655
661
  updateHistoryAvailability();
656
662
  return;
657
663
  }
664
+ onDirtyRef.current?.();
658
665
  historyPastRef.current.push(cloneHistorySnapshot(previousSnapshot));
659
666
  while (historyPastRef.current.length > Math.max(1, maxHistoryEntries)) {
660
667
  historyPastRef.current.shift();
@@ -1669,6 +1676,7 @@ function usePresentationKeyboard(input) {
1669
1676
  onToggleInkMarkup,
1670
1677
  onToggleToolbar,
1671
1678
  onShowAllSlides,
1679
+ onToggleSubtitles,
1672
1680
  onToggleBlackScreen,
1673
1681
  onToggleWhiteScreen,
1674
1682
  rehearsing,
@@ -1734,6 +1742,9 @@ function usePresentationKeyboard(input) {
1734
1742
  case "showAllSlides":
1735
1743
  onShowAllSlides?.();
1736
1744
  return;
1745
+ case "toggleSubtitles":
1746
+ onToggleSubtitles?.();
1747
+ return;
1737
1748
  case "toggleBlackScreen":
1738
1749
  onToggleBlackScreen?.();
1739
1750
  return;
@@ -1773,6 +1784,7 @@ function usePresentationKeyboard(input) {
1773
1784
  onToggleInkMarkup,
1774
1785
  onToggleToolbar,
1775
1786
  onShowAllSlides,
1787
+ onToggleSubtitles,
1776
1788
  onToggleBlackScreen,
1777
1789
  onToggleWhiteScreen,
1778
1790
  rehearsing,
@@ -2213,6 +2225,7 @@ function usePresentationMode(input) {
2213
2225
  onEraseAnnotations,
2214
2226
  onToggleInkMarkup,
2215
2227
  onToggleToolbar,
2228
+ onToggleSubtitles,
2216
2229
  onSaveRehearsalTimings,
2217
2230
  loopContinuously,
2218
2231
  showWithAnimation,
@@ -2420,6 +2433,7 @@ function usePresentationMode(input) {
2420
2433
  onToggleInkMarkup,
2421
2434
  onToggleToolbar,
2422
2435
  onShowAllSlides: openAllSlides,
2436
+ onToggleSubtitles,
2423
2437
  onToggleBlackScreen: () => presenterConsole.setBlackout(
2424
2438
  presenterConsole.snapshot.blackout === "black" ? "none" : "black"
2425
2439
  ),
@@ -2902,13 +2916,15 @@ function useFindReplace({
2902
2916
  }, [findResults, applyReplacements, performFind]);
2903
2917
  useEffect(() => {
2904
2918
  const handler = (event) => {
2905
- if ((event.metaKey || event.ctrlKey) && event.key === "f") {
2906
- if (mode !== "edit") {
2907
- return;
2908
- }
2909
- event.preventDefault();
2910
- setFindReplaceOpen((prev) => !prev);
2919
+ const { action } = mapEditorKey(event, {
2920
+ canEdit: mode === "edit",
2921
+ isTextInputTarget: isEditorTextInputTarget(event.target)
2922
+ });
2923
+ if (action !== "find") {
2924
+ return;
2911
2925
  }
2926
+ event.preventDefault();
2927
+ setFindReplaceOpen((prev) => !prev);
2912
2928
  };
2913
2929
  window.addEventListener("keydown", handler);
2914
2930
  return () => window.removeEventListener("keydown", handler);
@@ -3348,9 +3364,28 @@ function useDerivedElementState(input) {
3348
3364
  }
3349
3365
  return templateElementsBySlideId[activeSlide.id] ?? [];
3350
3366
  }, [activeSlide, templateElementsBySlideId]);
3367
+ const activeMaster = slideMasters[activeMasterIndex];
3368
+ const activeLayout = useMemo(
3369
+ () => resolveActiveLayout(activeMaster, activeLayoutIndex),
3370
+ [activeMaster, activeLayoutIndex]
3371
+ );
3372
+ const masterViewElements2 = useMemo(
3373
+ () => computeMasterViewElements(activeMaster, activeLayout),
3374
+ [activeMaster, activeLayout]
3375
+ );
3376
+ const notesMasterElements = useMemo(() => notesMaster?.elements ?? [], [notesMaster]);
3377
+ const handoutMasterElements = useMemo(() => handoutMaster?.elements ?? [], [handoutMaster]);
3351
3378
  const elementLookup = useMemo(
3352
- () => buildElementLookup(templateElements, activeSlide?.elements ?? []),
3353
- [activeSlide, templateElements]
3379
+ () => buildElementLookup(
3380
+ [
3381
+ ...templateElements,
3382
+ ...masterViewElements2,
3383
+ ...notesMasterElements,
3384
+ ...handoutMasterElements
3385
+ ],
3386
+ activeSlide?.elements ?? []
3387
+ ),
3388
+ [activeSlide, templateElements, masterViewElements2, notesMasterElements, handoutMasterElements]
3354
3389
  );
3355
3390
  const selectedElement = useMemo(() => {
3356
3391
  if (!selectedElementId) {
@@ -3367,17 +3402,6 @@ function useDerivedElementState(input) {
3367
3402
  () => effectiveSelectedIds.map((id) => elementLookup.get(id)).filter(Boolean),
3368
3403
  [effectiveSelectedIds, elementLookup]
3369
3404
  );
3370
- const activeMaster = slideMasters[activeMasterIndex];
3371
- const activeLayout = useMemo(
3372
- () => resolveActiveLayout(activeMaster, activeLayoutIndex),
3373
- [activeMaster, activeLayoutIndex]
3374
- );
3375
- const masterViewElements = useMemo(
3376
- () => computeMasterViewElements(activeMaster, activeLayout),
3377
- [activeMaster, activeLayout]
3378
- );
3379
- const notesMasterElements = useMemo(() => notesMaster?.elements ?? [], [notesMaster]);
3380
- const handoutMasterElements = useMemo(() => handoutMaster?.elements ?? [], [handoutMaster]);
3381
3405
  return {
3382
3406
  activeSlide,
3383
3407
  templateElements,
@@ -3388,7 +3412,7 @@ function useDerivedElementState(input) {
3388
3412
  selectedElements,
3389
3413
  activeMaster,
3390
3414
  activeLayout,
3391
- masterViewElements,
3415
+ masterViewElements: masterViewElements2,
3392
3416
  notesMasterElements,
3393
3417
  handoutMasterElements
3394
3418
  };
@@ -3416,6 +3440,7 @@ function useViewerCoreState(_input) {
3416
3440
  width: DEFAULT_CANVAS_WIDTH,
3417
3441
  height: DEFAULT_CANVAS_HEIGHT
3418
3442
  });
3443
+ const [slideSizeEmu, setSlideSizeEmu] = useState();
3419
3444
  const [activeSlideIndex, setActiveSlideIndex] = useState(0);
3420
3445
  const [selectedElementId, setSelectedElementId] = useState(null);
3421
3446
  const [selectedElementIds, setSelectedElementIds] = useState([]);
@@ -3514,6 +3539,8 @@ function useViewerCoreState(_input) {
3514
3539
  setTemplateElementsBySlideId,
3515
3540
  canvasSize,
3516
3541
  setCanvasSize,
3542
+ slideSizeEmu,
3543
+ setSlideSizeEmu,
3517
3544
  activeSlideIndex,
3518
3545
  setActiveSlideIndex,
3519
3546
  selectedElementId,
@@ -3718,6 +3745,7 @@ function useLoadContent({
3718
3745
  setTemplateElementsBySlideId,
3719
3746
  mediaDataUrls,
3720
3747
  setCanvasSize,
3748
+ setSlideSizeEmu,
3721
3749
  setHeaderFooter,
3722
3750
  setLayoutOptions,
3723
3751
  setSlideMasters,
@@ -3725,6 +3753,7 @@ function useLoadContent({
3725
3753
  setTableStyleMap,
3726
3754
  setThemeOptions,
3727
3755
  setCustomShows,
3756
+ setActiveCustomShowId,
3728
3757
  setSections,
3729
3758
  setPresentationProperties,
3730
3759
  setNotesMaster,
@@ -3884,6 +3913,13 @@ function useLoadContent({
3884
3913
  width: parsed.width ?? DEFAULT_CANVAS_WIDTH,
3885
3914
  height: parsed.height ?? DEFAULT_CANVAS_HEIGHT
3886
3915
  });
3916
+ setSlideSizeEmu(
3917
+ typeof parsed.widthEmu === "number" && typeof parsed.heightEmu === "number" && parsed.widthEmu > 0 && parsed.heightEmu > 0 ? {
3918
+ widthEmu: parsed.widthEmu,
3919
+ heightEmu: parsed.heightEmu,
3920
+ type: parsed.slideSizeType ?? ""
3921
+ } : void 0
3922
+ );
3887
3923
  setHeaderFooter(parsed.headerFooter ?? {});
3888
3924
  setLayoutOptions(parsed.layoutOptions ?? []);
3889
3925
  setSlideMasters(parsed.slideMasters ?? []);
@@ -3891,6 +3927,9 @@ function useLoadContent({
3891
3927
  setTableStyleMap(parsed.tableStyleMap);
3892
3928
  setThemeOptions(parsed.themeOptions ?? []);
3893
3929
  setCustomShows(parsed.customShows ?? []);
3930
+ setActiveCustomShowId(
3931
+ resolveAuthoredCustomShowId(parsed.presentationProperties, parsed.customShows) ?? null
3932
+ );
3894
3933
  setSections(parsed.sections ?? []);
3895
3934
  setPresentationProperties(parsed.presentationProperties ?? {});
3896
3935
  setNotesMaster(parsed.notesMaster);
@@ -3959,7 +3998,8 @@ function useAutosave(input) {
3959
3998
  filePath,
3960
3999
  serializeSlides,
3961
4000
  intervalSeconds = DEFAULT_AUTOSAVE_INTERVAL_SECONDS,
3962
- enabled = true
4001
+ enabled = true,
4002
+ getChangeSources
3963
4003
  } = input;
3964
4004
  const [autosaveStatus, setAutosaveStatus] = useState({
3965
4005
  state: "idle"
@@ -3968,6 +4008,8 @@ function useAutosave(input) {
3968
4008
  const filePathRef = useRef(filePath);
3969
4009
  const serializeRef = useRef(serializeSlides);
3970
4010
  const isSavingRef = useRef(false);
4011
+ const changeSourcesRef = useRef(getChangeSources);
4012
+ const lastSnapshotRef = useRef(void 0);
3971
4013
  useEffect(() => {
3972
4014
  isDirtyRef.current = isDirty;
3973
4015
  }, [isDirty]);
@@ -3977,14 +4019,22 @@ function useAutosave(input) {
3977
4019
  useEffect(() => {
3978
4020
  serializeRef.current = serializeSlides;
3979
4021
  }, [serializeSlides]);
3980
- const doAutosave = useCallback(async () => {
3981
- if (!filePathRef.current) {
3982
- return;
3983
- }
3984
- if (!isDirtyRef.current) {
3985
- return;
3986
- }
3987
- if (isSavingRef.current) {
4022
+ useEffect(() => {
4023
+ changeSourcesRef.current = getChangeSources;
4024
+ }, [getChangeSources]);
4025
+ const doAutosave = useCallback(async (options) => {
4026
+ const path = filePathRef.current;
4027
+ if (!path) {
4028
+ return;
4029
+ }
4030
+ const sources = changeSourcesRef.current?.() ?? [];
4031
+ if (!shouldWriteAutosaveSnapshot({
4032
+ filePath: path,
4033
+ isDirty: isDirtyRef.current,
4034
+ saving: isSavingRef.current,
4035
+ sources: options?.polled === true ? sources : [],
4036
+ lastSnapshot: lastSnapshotRef.current
4037
+ })) {
3988
4038
  return;
3989
4039
  }
3990
4040
  isSavingRef.current = true;
@@ -3996,7 +4046,8 @@ function useAutosave(input) {
3996
4046
  isSavingRef.current = false;
3997
4047
  return;
3998
4048
  }
3999
- await saveAutosaveSnapshot(filePathRef.current, data);
4049
+ await saveAutosaveSnapshot(path, data);
4050
+ lastSnapshotRef.current = autosaveSnapshotMark(path, sources);
4000
4051
  setAutosaveStatus({ state: "saved", timestamp: Date.now() });
4001
4052
  } catch (err) {
4002
4053
  setAutosaveStatus({
@@ -4022,13 +4073,13 @@ function useAutosave(input) {
4022
4073
  setAutosaveStatus((prev) => prev.state === "disabled" ? { state: "idle" } : prev);
4023
4074
  const ms = computeAutosaveIntervalMs(intervalSeconds);
4024
4075
  const id = setInterval(() => {
4025
- void doAutosave();
4076
+ void doAutosave({ polled: true });
4026
4077
  }, ms);
4027
4078
  return () => clearInterval(id);
4028
4079
  }, [enabled, filePath, intervalSeconds, doAutosave]);
4029
4080
  return {
4030
4081
  autosaveStatus,
4031
- triggerAutosave: doAutosave
4082
+ triggerAutosave: () => doAutosave()
4032
4083
  };
4033
4084
  }
4034
4085
  function useElementOperations(input) {
@@ -4039,6 +4090,7 @@ function useElementOperations(input) {
4039
4090
  selectedElementId,
4040
4091
  editTemplateMode,
4041
4092
  templateElements,
4093
+ masterView,
4042
4094
  history,
4043
4095
  setSlides,
4044
4096
  setTemplateElementsBySlideId,
@@ -4059,8 +4111,33 @@ function useElementOperations(input) {
4059
4111
  setInlineEditingElementId(null);
4060
4112
  setContextMenuState(null);
4061
4113
  }, [applySelection, setInlineEditingElementId, setContextMenuState]);
4114
+ const applyMasterViewWrite = useCallback(
4115
+ (write) => {
4116
+ if (!write || !masterView) {
4117
+ return false;
4118
+ }
4119
+ if (write.slideMasters) {
4120
+ masterView.setSlideMasters(write.slideMasters);
4121
+ }
4122
+ if (write.notesMaster) {
4123
+ masterView.setNotesMaster(write.notesMaster);
4124
+ }
4125
+ if (write.handoutMaster) {
4126
+ masterView.setHandoutMaster(write.handoutMaster);
4127
+ }
4128
+ history.markDirty();
4129
+ return true;
4130
+ },
4131
+ [history, masterView]
4132
+ );
4062
4133
  const updateElementById = useCallback(
4063
4134
  (elementId, updates) => {
4135
+ if (masterView?.target) {
4136
+ applyMasterViewWrite(
4137
+ updateMasterViewElement(masterView, masterView.target, elementId, updates)
4138
+ );
4139
+ return;
4140
+ }
4064
4141
  if (isTemplateElementId(elementId)) {
4065
4142
  const slideId = activeSlide?.id;
4066
4143
  if (slideId) {
@@ -4088,7 +4165,15 @@ function useElementOperations(input) {
4088
4165
  }
4089
4166
  history.markDirty();
4090
4167
  },
4091
- [activeSlide?.id, activeSlideIndex, history, setSlides, setTemplateElementsBySlideId]
4168
+ [
4169
+ activeSlide?.id,
4170
+ activeSlideIndex,
4171
+ applyMasterViewWrite,
4172
+ history,
4173
+ masterView,
4174
+ setSlides,
4175
+ setTemplateElementsBySlideId
4176
+ ]
4092
4177
  );
4093
4178
  const updateSelectedElement = useCallback(
4094
4179
  (updates) => {
@@ -4176,9 +4261,20 @@ function useElementOperations(input) {
4176
4261
  },
4177
4262
  [setSlides]
4178
4263
  );
4179
- const activeElements = editTemplateMode ? templateElements : activeSlide?.elements ?? [];
4264
+ const masterViewTarget = masterView?.target ?? null;
4265
+ const activeElements = masterViewTarget ? masterViewElements(masterView, masterViewTarget) : editTemplateMode ? templateElements : activeSlide?.elements ?? [];
4180
4266
  const updateActiveElements = useCallback(
4181
4267
  (updater) => {
4268
+ if (masterView?.target) {
4269
+ applyMasterViewWrite(
4270
+ replaceMasterViewElements(
4271
+ masterView,
4272
+ masterView.target,
4273
+ updater(masterViewElements(masterView, masterView.target))
4274
+ )
4275
+ );
4276
+ return;
4277
+ }
4182
4278
  if (editTemplateMode) {
4183
4279
  const slideId = activeSlide?.id;
4184
4280
  if (!slideId) {
@@ -4196,7 +4292,15 @@ function useElementOperations(input) {
4196
4292
  );
4197
4293
  }
4198
4294
  },
4199
- [editTemplateMode, activeSlide?.id, activeSlideIndex, setSlides, setTemplateElementsBySlideId]
4295
+ [
4296
+ applyMasterViewWrite,
4297
+ editTemplateMode,
4298
+ masterView,
4299
+ activeSlide?.id,
4300
+ activeSlideIndex,
4301
+ setSlides,
4302
+ setTemplateElementsBySlideId
4303
+ ]
4200
4304
  );
4201
4305
  const serializeSlides = useCallback(async () => {
4202
4306
  return null;
@@ -4267,13 +4371,23 @@ function useSectionOperations(input) {
4267
4371
  },
4268
4372
  [sections, slides, setSlides, setSections, markDirty]
4269
4373
  );
4374
+ const toggleSectionCollapse = useCallback(
4375
+ (sectionId) => {
4376
+ setSections(
4377
+ (prev) => prev.map((sec) => sec.id === sectionId ? { ...sec, collapsed: !sec.collapsed } : sec)
4378
+ );
4379
+ markDirty();
4380
+ },
4381
+ [setSections, markDirty]
4382
+ );
4270
4383
  return {
4271
4384
  addSection: addSection2,
4272
4385
  renameSection: renameSection2,
4273
4386
  deleteSection: deleteSection2,
4274
4387
  moveSectionUp: moveSectionUp2,
4275
4388
  moveSectionDown: moveSectionDown2,
4276
- moveSlidesToSection: moveSlidesToSection2
4389
+ moveSlidesToSection: moveSlidesToSection2,
4390
+ toggleSectionCollapse
4277
4391
  };
4278
4392
  }
4279
4393
 
@@ -4293,11 +4407,6 @@ function computeResizeGeometry(handle, startX, startY, startWidth, startHeight,
4293
4407
  }
4294
4408
  return snapBoxToGrid(box, handle, gridSpacingPx, MIN_ELEMENT_SIZE);
4295
4409
  }
4296
- function computeAdjustmentValue(startAdjustment, dx, startWidth) {
4297
- const range = startWidth || 200;
4298
- const delta = dx / range;
4299
- return Math.max(0, Math.min(1, startAdjustment + delta));
4300
- }
4301
4410
  function resolveLiveTarget(input) {
4302
4411
  const { livePatcher, editTemplateMode, activeSlide } = input;
4303
4412
  if (!livePatcher || editTemplateMode || !livePatcher.isActive()) {
@@ -4496,13 +4605,14 @@ function processResizeMove(e, rs, editorScale, snapToGrid, gridSpacingPx, live)
4496
4605
  }
4497
4606
  function processAdjustmentMove(e, adj, editorScale, updateElementById) {
4498
4607
  const dx = (e.clientX - adj.startClientX) / editorScale;
4499
- const newValue = computeAdjustmentValue(adj.startAdjustment, dx, adj.startWidth);
4500
- if (!adj.moved && Math.abs(dx) > 2) {
4608
+ const dy = (e.clientY - adj.startClientY) / editorScale;
4609
+ const adjustments = getDraggedShapeAdjustments(adj, dx, dy);
4610
+ if (!adj.moved && Math.hypot(dx, dy) > 2) {
4501
4611
  adj.moved = true;
4502
4612
  }
4503
4613
  if (adj.moved) {
4504
4614
  updateElementById(adj.elementId, {
4505
- shapeAdjustments: { [adj.key]: newValue }
4615
+ shapeAdjustments: adjustments
4506
4616
  });
4507
4617
  }
4508
4618
  }
@@ -4828,7 +4938,7 @@ function useCanvasInteractions(input) {
4828
4938
  justInteractedRef.current = false;
4829
4939
  } else {
4830
4940
  const el = elementLookup.get(elementId);
4831
- if (el && hasTextProperties(el) && !el.locks?.noTextEdit) {
4941
+ if (el && hasTextProperties(el) && canInteractWithElement(el, "textEdit")) {
4832
4942
  if (!openEquationEditorForElement(el)) {
4833
4943
  setInlineEditingElementId(elementId);
4834
4944
  setInlineEditingText(el.text ?? "");
@@ -4847,7 +4957,7 @@ function useCanvasInteractions(input) {
4847
4957
  if (openEquationEditorForElement(el)) {
4848
4958
  return;
4849
4959
  }
4850
- if (hasTextProperties(el)) {
4960
+ if (hasTextProperties(el) && canInteractWithElement(el, "textEdit")) {
4851
4961
  setInlineEditingElementId(elementId);
4852
4962
  setInlineEditingText(el.text ?? "");
4853
4963
  }
@@ -4870,9 +4980,13 @@ function useCanvasInteractions(input) {
4870
4980
  justSelectedRef.current = false;
4871
4981
  }
4872
4982
  const ids = !wasSelected ? [elementId] : effectiveSelectedIds.length ? effectiveSelectedIds : [elementId];
4983
+ const movableIds = filterInteractableIds(ids, (id) => elementLookup.get(id), "move");
4984
+ if (movableIds.length === 0) {
4985
+ return;
4986
+ }
4873
4987
  const startPositions = {};
4874
4988
  const domEls = /* @__PURE__ */ new Map();
4875
- for (const id of ids) {
4989
+ for (const id of movableIds) {
4876
4990
  const el = elementLookup.get(id);
4877
4991
  if (el) {
4878
4992
  startPositions[id] = { x: el.x, y: el.y };
@@ -4936,7 +5050,7 @@ function useCanvasInteractions(input) {
4936
5050
  const handleResizePointerDown = (elementId, e, handle) => {
4937
5051
  e.stopPropagation();
4938
5052
  const el = elementLookup.get(elementId);
4939
- if (!el) {
5053
+ if (!el || !canInteractWithElement(el, "resize")) {
4940
5054
  return;
4941
5055
  }
4942
5056
  resizeStateRef.current = {
@@ -4958,7 +5072,7 @@ function useCanvasInteractions(input) {
4958
5072
  };
4959
5073
  const handleRotate = (elementId, rotationDeg) => {
4960
5074
  const el = elementLookup.get(elementId);
4961
- if (!el || el.locks?.noRotation) {
5075
+ if (!el || !canInteractWithElement(el, "rotate")) {
4962
5076
  return;
4963
5077
  }
4964
5078
  ops.updateElementById(elementId, { rotation: rotationDeg });
@@ -4975,30 +5089,18 @@ function useCanvasInteractions(input) {
4975
5089
  const handleFormatText = (updates) => {
4976
5090
  ops.updateSelectedTextStyle(updates);
4977
5091
  };
4978
- const handleAdjustmentPointerDown = (elementId, e) => {
5092
+ const handleAdjustmentPointerDown = (elementId, e, descriptor) => {
4979
5093
  e.stopPropagation();
4980
5094
  const el = elementLookup.get(elementId);
4981
- if (!el || !("shapeType" in el) || !("shapeAdjustments" in el)) {
5095
+ if (!el || !canInteractWithElement(el, "adjustHandle")) {
4982
5096
  return;
4983
5097
  }
4984
- const adjEntries = Object.entries(
4985
- el.shapeAdjustments ?? {}
5098
+ shapeAdjustmentDragStateRef.current = beginShapeAdjustment(
5099
+ el,
5100
+ descriptor,
5101
+ e.clientX,
5102
+ e.clientY
4986
5103
  );
4987
- if (!adjEntries.length) {
4988
- return;
4989
- }
4990
- const [key, value] = adjEntries[0];
4991
- shapeAdjustmentDragStateRef.current = {
4992
- elementId,
4993
- key,
4994
- shapeType: el.shapeType ?? "rect",
4995
- startClientX: e.clientX,
4996
- startClientY: e.clientY,
4997
- startAdjustment: value,
4998
- startWidth: el.width,
4999
- startHeight: el.height,
5000
- moved: false
5001
- };
5002
5104
  };
5003
5105
  return {
5004
5106
  handleElementClick,
@@ -5906,16 +6008,20 @@ function useGroupAlignLayerHandlers(input) {
5906
6008
  }
5907
6009
  const group = selectedElement;
5908
6010
  const intoTemplate = isTemplateElementId(group.id);
5909
- const ungrouped = group.children.map((child) => ({
5910
- ...structuredClone(child),
5911
- // Keep child ids in the same store as the group so later edits route
5912
- // correctly: template groups yield template-prefixed child ids.
5913
- id: intoTemplate ? makeCloneId(true, child.id || group.id) : child.id || generateElementId(),
5914
- x: child.x + group.x,
5915
- y: child.y + group.y
5916
- }));
5917
- ops.updateActiveElements((els) => [...els.filter((el) => el.id !== group.id), ...ungrouped]);
5918
- setSelectedElementIds(ungrouped.map((el) => el.id));
6011
+ const childIds = group.children.map(
6012
+ (child) => intoTemplate ? makeCloneId(true, child.id || group.id) : child.id || generateElementId()
6013
+ );
6014
+ const { elements, childIds: usedIds } = ungroupElements(
6015
+ ops.activeElements,
6016
+ group.id,
6017
+ childIds,
6018
+ { intoTemplate }
6019
+ );
6020
+ if (usedIds.length === 0) {
6021
+ return;
6022
+ }
6023
+ ops.updateActiveElements(() => elements);
6024
+ setSelectedElementIds(usedIds);
5919
6025
  history.markDirty();
5920
6026
  };
5921
6027
  const handleFlip = (direction) => {
@@ -6909,12 +7015,14 @@ function useExportSaveAs(input) {
6909
7015
  appProperties,
6910
7016
  customProperties,
6911
7017
  tagCollections,
7018
+ slideMasters,
6912
7019
  notesMaster,
6913
7020
  handoutMaster,
6914
7021
  guides,
6915
7022
  activeSlideIndexForGuides,
6916
7023
  theme,
6917
7024
  canvasSize,
7025
+ slideSizeEmu,
6918
7026
  modalControls,
6919
7027
  password
6920
7028
  } = input;
@@ -6938,7 +7046,7 @@ function useExportSaveAs(input) {
6938
7046
  setExportProgress(10);
6939
7047
  setExportStatusMessage("Adding presentation...");
6940
7048
  const pptxData = await serializeSlides();
6941
- const pptxFilename = filePath ? filePath.replace(/\\/gu, "/").split("/").pop() ?? "presentation.pptx" : "presentation.pptx";
7049
+ const pptxFilename = savedPresentationFileName(filePath);
6942
7050
  if (pptxData) {
6943
7051
  pkgFolder.file(pptxFilename, pptxData);
6944
7052
  }
@@ -6954,8 +7062,7 @@ function useExportSaveAs(input) {
6954
7062
  const zipBlob = await zip.generateAsync({ type: "blob" });
6955
7063
  setExportProgress(95);
6956
7064
  setExportStatusMessage("Downloading...");
6957
- const baseName = pptxFilename.replace(/\.[^.]+$/u, "");
6958
- downloadBlob(zipBlob, `${baseName}-package.zip`);
7065
+ downloadBlob(zipBlob, `${presentationBaseName(pptxFilename)}-package.zip`);
6959
7066
  setExportProgress(100);
6960
7067
  } catch (err) {
6961
7068
  if (err.name !== "AbortError") {
@@ -6971,16 +7078,15 @@ function useExportSaveAs(input) {
6971
7078
  if (!handler) {
6972
7079
  return;
6973
7080
  }
6974
- const ext = format === "ppsx" ? "ppsx" : format === "pptm" ? "pptm" : "pptx";
6975
- const baseName = filePath ? filePath.replace(/\\/gu, "/").split("/").pop()?.replace(/\.[^.]+$/u, "") ?? "presentation" : "presentation";
7081
+ const downloadName = savedPresentationFileName(filePath, format);
6976
7082
  try {
6977
7083
  const data = await buildSaveAsData(handler, format);
6978
7084
  const blob = new Blob([data], {
6979
7085
  type: "application/vnd.openxmlformats-officedocument.presentationml.presentation"
6980
7086
  });
6981
- downloadBlob(blob, `${baseName}.${ext}`);
7087
+ downloadBlob(blob, downloadName);
6982
7088
  } catch (err) {
6983
- console.error(`[PowerPointViewer] Save as .${ext} failed:`, err);
7089
+ console.error(`[PowerPointViewer] Save as .${format} failed:`, err);
6984
7090
  }
6985
7091
  };
6986
7092
  const buildSaveAsData = async (handler, format) => {
@@ -7000,6 +7106,9 @@ function useExportSaveAs(input) {
7000
7106
  });
7001
7107
  const slidesToSave = buildSaveSlides(slidesWithGuides, templateElementsBySlideId);
7002
7108
  const saveOptions = {
7109
+ // Save As shares Save's slide-size decision; leaving it out here is how
7110
+ // a preset picked in the inspector reached one save path and not the other.
7111
+ slideSize: resolveSlideSizeSelection({ current: slideSizeEmu, canvas: canvasSize }).size,
7003
7112
  headerFooter,
7004
7113
  presentationProperties,
7005
7114
  customShows: customShows.length > 0 ? customShows : void 0,
@@ -7008,27 +7117,23 @@ function useExportSaveAs(input) {
7008
7117
  appProperties: appProperties ?? void 0,
7009
7118
  customProperties: customProperties.length > 0 ? customProperties : void 0,
7010
7119
  tags: tagCollections.length > 0 ? tagCollections : void 0,
7120
+ slideMasters,
7011
7121
  notesMaster,
7012
7122
  handoutMaster,
7013
7123
  outputFormat: format
7014
7124
  };
7015
- if (password) {
7016
- return handler.saveEncrypted(
7017
- slidesToSave,
7018
- password,
7019
- saveOptions
7020
- );
7021
- }
7022
- return handler.save(slidesToSave, saveOptions);
7125
+ return saveDeckWithPassword(handler, slidesToSave, saveOptions, password);
7023
7126
  };
7024
7127
  const handleExportJson = () => {
7025
7128
  const sourceName = filePath ? filePath.replace(/\\/gu, "/").split("/").pop() : void 0;
7129
+ const slideSize = resolveSlideSizeSelection({ current: slideSizeEmu, canvas: canvasSize }).size;
7026
7130
  const data = {
7027
7131
  slides: buildSaveSlides(slides, templateElementsBySlideId),
7028
7132
  width: canvasSize.width,
7029
7133
  height: canvasSize.height,
7030
- widthEmu: guidePxToEmu(canvasSize.width),
7031
- heightEmu: guidePxToEmu(canvasSize.height),
7134
+ widthEmu: slideSize.widthEmu,
7135
+ heightEmu: slideSize.heightEmu,
7136
+ slideSizeType: slideSize.type === "" ? void 0 : slideSize.type,
7032
7137
  theme,
7033
7138
  headerFooter,
7034
7139
  presentationProperties,
@@ -7105,12 +7210,14 @@ function useExportHandlers(input) {
7105
7210
  appProperties,
7106
7211
  customProperties,
7107
7212
  tagCollections,
7213
+ slideMasters,
7108
7214
  notesMaster,
7109
7215
  handoutMaster,
7110
7216
  guides,
7111
7217
  activeSlideIndexForGuides,
7112
7218
  theme,
7113
7219
  canvasSize,
7220
+ slideSizeEmu,
7114
7221
  password
7115
7222
  } = input;
7116
7223
  const [exportModalOpen, setExportModalOpen] = useState(false);
@@ -7146,12 +7253,14 @@ function useExportHandlers(input) {
7146
7253
  appProperties,
7147
7254
  customProperties,
7148
7255
  tagCollections,
7256
+ slideMasters,
7149
7257
  notesMaster,
7150
7258
  handoutMaster,
7151
7259
  guides,
7152
7260
  activeSlideIndexForGuides,
7153
7261
  theme,
7154
7262
  canvasSize,
7263
+ slideSizeEmu,
7155
7264
  modalControls,
7156
7265
  password
7157
7266
  });
@@ -8097,7 +8206,8 @@ function useViewerDialogs(input) {
8097
8206
  preMasterMode,
8098
8207
  hasDigitalSignatures,
8099
8208
  isDirty,
8100
- history
8209
+ history,
8210
+ embeddedFontFamilies
8101
8211
  } = input;
8102
8212
  const [isSmartArtDialogOpen, setIsSmartArtDialogOpen] = useState(false);
8103
8213
  const [isEquationDialogOpen, setIsEquationDialogOpen] = useState(false);
@@ -8115,7 +8225,15 @@ function useViewerDialogs(input) {
8115
8225
  const [editingEquationOmml, setEditingEquationOmml] = useState(
8116
8226
  null
8117
8227
  );
8118
- const [embedFontsEnabled, setEmbedFontsEnabled] = useState(false);
8228
+ const embeddedFontKey = (embeddedFontFamilies ?? []).join("\0");
8229
+ const fontEmbedding = useMemo(
8230
+ () => describeFontEmbedding(embeddedFontKey ? embeddedFontKey.split("\0") : []),
8231
+ [embeddedFontKey]
8232
+ );
8233
+ const [embedFontsEnabled, setEmbedFontsEnabled] = useState(fontEmbedding.initialEnabled);
8234
+ useEffect(() => {
8235
+ setEmbedFontsEnabled(fontEmbedding.initialEnabled);
8236
+ }, [fontEmbedding]);
8119
8237
  const [isNarrowViewport, setIsNarrowViewport] = useState(
8120
8238
  () => typeof window !== "undefined" ? isMobileViewport(window.innerWidth, window.innerHeight, viewportIsTouch()) : false
8121
8239
  );
@@ -8282,6 +8400,7 @@ function useViewerDialogs(input) {
8282
8400
  setEditingEquationOmml,
8283
8401
  embedFontsEnabled,
8284
8402
  setEmbedFontsEnabled,
8403
+ fontEmbedding,
8285
8404
  isNarrowViewport,
8286
8405
  handleEnterMasterView,
8287
8406
  handleCloseMasterView,
@@ -8311,7 +8430,7 @@ function computeVisibleSlideIndexes(slides, activeCustomShowId, customShows) {
8311
8430
  }
8312
8431
  function computeSlideSectionGroups(slides, sections) {
8313
8432
  return groupSlidesBySection(sections, slides).map((group) => ({
8314
- id: group.section?.id ?? (sections.length > 0 ? UNGROUPED_SECTION_ID : "default"),
8433
+ id: group.section?.id ?? (sections.length > 0 ? UNGROUPED_SECTION_ID : DEFAULT_SECTION_GROUP_ID),
8315
8434
  label: group.section?.name ?? (sections.length > 0 ? "Ungrouped Slides" : "Slides"),
8316
8435
  slideIndexes: group.slideIndexes,
8317
8436
  ...group.section?.color !== void 0 ? { color: group.section.color } : {},
@@ -8319,30 +8438,15 @@ function computeSlideSectionGroups(slides, sections) {
8319
8438
  }));
8320
8439
  }
8321
8440
  function computeMasterPseudoSlide(mode, activeLayout, activeMaster) {
8322
- if (mode !== "master") {
8441
+ if (mode !== "master" || !activeMaster) {
8323
8442
  return void 0;
8324
8443
  }
8325
- if (activeLayout) {
8326
- return {
8327
- id: activeLayout.path,
8328
- rId: "",
8329
- slideNumber: 0,
8330
- elements: activeLayout.elements ?? [],
8331
- backgroundColor: activeLayout.backgroundColor ?? activeMaster?.backgroundColor,
8332
- backgroundImage: activeLayout.backgroundImage ?? activeMaster?.backgroundImage
8333
- };
8334
- }
8335
- if (activeMaster) {
8336
- return {
8337
- id: activeMaster.path,
8338
- rId: "",
8339
- slideNumber: 0,
8340
- elements: activeMaster.elements ?? [],
8341
- backgroundColor: activeMaster.backgroundColor,
8342
- backgroundImage: activeMaster.backgroundImage
8343
- };
8344
- }
8345
- return void 0;
8444
+ const document2 = activeLayout ? { slideMasters: [{ ...activeMaster, layouts: [activeLayout] }] } : { slideMasters: [activeMaster] };
8445
+ return masterViewPseudoSlide(document2, {
8446
+ tab: "slides",
8447
+ masterIndex: 0,
8448
+ layoutIndex: activeLayout ? 0 : null
8449
+ });
8346
8450
  }
8347
8451
  function useDerivedSlideState(input) {
8348
8452
  const { t } = useTranslation();
@@ -8369,7 +8473,7 @@ function useDerivedSlideState(input) {
8369
8473
  if (group.id === UNGROUPED_SECTION_ID && group.label === "Ungrouped Slides") {
8370
8474
  return { ...group, label: t("pptx.slides.ungroupedSlides") };
8371
8475
  }
8372
- if (group.id === "default" && group.label === "Slides") {
8476
+ if (group.id === DEFAULT_SECTION_GROUP_ID && group.label === "Slides") {
8373
8477
  return { ...group, label: t("pptx.sections.slides") };
8374
8478
  }
8375
8479
  return group;
@@ -8523,6 +8627,8 @@ function useSerialize(input) {
8523
8627
  slides,
8524
8628
  templateElementsBySlideId,
8525
8629
  activeSlideIndex,
8630
+ canvasSize,
8631
+ slideSizeEmu,
8526
8632
  guides,
8527
8633
  headerFooter,
8528
8634
  presentationProperties,
@@ -8532,12 +8638,15 @@ function useSerialize(input) {
8532
8638
  appProperties,
8533
8639
  customProperties,
8534
8640
  tagCollections,
8641
+ slideMasters,
8535
8642
  notesMaster,
8536
8643
  handoutMaster,
8537
8644
  handlerRef,
8538
8645
  inlineEditingElementIdRef,
8539
8646
  inlineEditingTextRef,
8540
- password
8647
+ password,
8648
+ embedFonts = true,
8649
+ purpose
8541
8650
  } = input;
8542
8651
  return useCallback(async () => {
8543
8652
  const handler = handlerRef.current;
@@ -8578,6 +8687,10 @@ function useSerialize(input) {
8578
8687
  });
8579
8688
  const slidesToSave = buildSaveSlides(slidesWithGuides, templateElementsBySlideId);
8580
8689
  const saveOptions = {
8690
+ // Without this the Slide Size card edited a viewer-only pixel value and
8691
+ // the saved `p:sldSz` never moved: passing `slideSize` is the only way a
8692
+ // slide-size edit reaches the file.
8693
+ slideSize: resolveSlideSizeSelection({ current: slideSizeEmu, canvas: canvasSize }).size,
8581
8694
  headerFooter,
8582
8695
  presentationProperties,
8583
8696
  customShows: customShows.length > 0 ? customShows : void 0,
@@ -8586,16 +8699,17 @@ function useSerialize(input) {
8586
8699
  appProperties,
8587
8700
  customProperties: customProperties.length > 0 ? customProperties : void 0,
8588
8701
  tags: tagCollections.length > 0 ? tagCollections : void 0,
8702
+ slideMasters,
8589
8703
  notesMaster,
8590
- handoutMaster
8704
+ handoutMaster,
8705
+ ...embeddedFontSaveOptions(embedFonts)
8591
8706
  };
8592
- if (password) {
8593
- return handler.saveEncrypted(slidesToSave, password, saveOptions);
8594
- }
8595
- return handler.save(slidesToSave, saveOptions);
8707
+ return saveDeckWithPassword(handler, slidesToSave, saveOptions, { password, purpose });
8596
8708
  }, [
8597
8709
  slides,
8598
8710
  templateElementsBySlideId,
8711
+ canvasSize,
8712
+ slideSizeEmu,
8599
8713
  headerFooter,
8600
8714
  presentationProperties,
8601
8715
  customShows,
@@ -8604,6 +8718,7 @@ function useSerialize(input) {
8604
8718
  appProperties,
8605
8719
  customProperties,
8606
8720
  tagCollections,
8721
+ slideMasters,
8607
8722
  notesMaster,
8608
8723
  handoutMaster,
8609
8724
  guides,
@@ -8611,7 +8726,9 @@ function useSerialize(input) {
8611
8726
  handlerRef,
8612
8727
  inlineEditingElementIdRef,
8613
8728
  inlineEditingTextRef,
8614
- password
8729
+ password,
8730
+ embedFonts,
8731
+ purpose
8615
8732
  ]);
8616
8733
  }
8617
8734
 
@@ -8727,7 +8844,8 @@ function usePresentationSetup(input) {
8727
8844
  history,
8728
8845
  endWithBlackSlide = true,
8729
8846
  promptKeepInkAnnotations = true,
8730
- popupToolbarEnabled = true
8847
+ popupToolbarEnabled = true,
8848
+ onToggleSubtitles
8731
8849
  } = input;
8732
8850
  const actionSoundHandlerRef = useRef(null);
8733
8851
  const [, setShowKeepAnnotationsDialog] = useState(false);
@@ -8806,6 +8924,7 @@ function usePresentationSetup(input) {
8806
8924
  onEraseAnnotations: () => annotations.clearAnnotations(),
8807
8925
  onToggleInkMarkup: () => annotations.setInkMarkupVisible(!annotations.inkMarkupVisible),
8808
8926
  onToggleToolbar: () => annotations.setToolbarVisible(!annotations.toolbarVisible),
8927
+ onToggleSubtitles,
8809
8928
  onSaveRehearsalTimings: (timings) => {
8810
8929
  setSlides((prev) => applyRehearsalTimings(prev, timings));
8811
8930
  history.markDirty();
@@ -8837,6 +8956,33 @@ function useEditorOperations(input) {
8837
8956
  handlerRef,
8838
8957
  transformCommittedText
8839
8958
  } = input;
8959
+ const masterView = useMemo(
8960
+ () => ({
8961
+ target: mode === "master" ? {
8962
+ tab: state.masterViewTab,
8963
+ masterIndex: state.activeMasterIndex,
8964
+ layoutIndex: state.activeLayoutIndex
8965
+ } : null,
8966
+ slideMasters: state.slideMasters,
8967
+ notesMaster: state.notesMaster,
8968
+ handoutMaster: state.handoutMaster,
8969
+ setSlideMasters: state.setSlideMasters,
8970
+ setNotesMaster: state.setNotesMaster,
8971
+ setHandoutMaster: state.setHandoutMaster
8972
+ }),
8973
+ [
8974
+ mode,
8975
+ state.masterViewTab,
8976
+ state.activeMasterIndex,
8977
+ state.activeLayoutIndex,
8978
+ state.slideMasters,
8979
+ state.notesMaster,
8980
+ state.handoutMaster,
8981
+ state.setSlideMasters,
8982
+ state.setNotesMaster,
8983
+ state.setHandoutMaster
8984
+ ]
8985
+ );
8840
8986
  const ops = useElementOperations({
8841
8987
  activeSlide,
8842
8988
  activeSlideIndex,
@@ -8844,6 +8990,7 @@ function useEditorOperations(input) {
8844
8990
  selectedElementId,
8845
8991
  editTemplateMode: state.editTemplateMode,
8846
8992
  templateElements: state.templateElements,
8993
+ masterView,
8847
8994
  history,
8848
8995
  setSlides: state.setSlides,
8849
8996
  setTemplateElementsBySlideId: state.setTemplateElementsBySlideId,
@@ -9054,6 +9201,7 @@ function useIOHandlers(input) {
9054
9201
  zoom,
9055
9202
  handlerRef,
9056
9203
  serializeSlides,
9204
+ serializeForRecovery,
9057
9205
  setContent,
9058
9206
  onContentChange,
9059
9207
  password
@@ -9076,12 +9224,14 @@ function useIOHandlers(input) {
9076
9224
  appProperties: state.appProperties ?? null,
9077
9225
  customProperties: state.customProperties,
9078
9226
  tagCollections: state.tagCollections,
9227
+ slideMasters: state.slideMasters,
9079
9228
  notesMaster: state.notesMaster,
9080
9229
  handoutMaster: state.handoutMaster,
9081
9230
  guides: state.guides,
9082
9231
  activeSlideIndexForGuides: state.activeSlideIndex,
9083
9232
  theme: state.theme,
9084
9233
  canvasSize,
9234
+ slideSizeEmu: state.slideSizeEmu,
9085
9235
  password
9086
9236
  });
9087
9237
  const printHandlers = usePrintHandlers({
@@ -9092,7 +9242,9 @@ function useIOHandlers(input) {
9092
9242
  });
9093
9243
  const themeHandlers = useThemeHandlers({
9094
9244
  handlerRef,
9095
- serializeSlides,
9245
+ // Theme apply re-serialises and re-parses through `setContent`, so these
9246
+ // bytes go back into our own loader: plaintext, never the protected save.
9247
+ serializeSlides: serializeForRecovery,
9096
9248
  setContent,
9097
9249
  onContentChange,
9098
9250
  setTheme: state.setTheme,
@@ -9285,6 +9437,8 @@ function useContentLifecycle(input) {
9285
9437
  content,
9286
9438
  filePath,
9287
9439
  autosaveEnabled = true,
9440
+ autosaveIntervalSeconds,
9441
+ embedFonts = true,
9288
9442
  slides,
9289
9443
  state,
9290
9444
  history,
@@ -9302,6 +9456,7 @@ function useContentLifecycle(input) {
9302
9456
  setTemplateElementsBySlideId: state.setTemplateElementsBySlideId,
9303
9457
  mediaDataUrls: state.mediaDataUrls,
9304
9458
  setCanvasSize: state.setCanvasSize,
9459
+ setSlideSizeEmu: state.setSlideSizeEmu,
9305
9460
  setHeaderFooter: state.setHeaderFooter,
9306
9461
  setLayoutOptions: state.setLayoutOptions,
9307
9462
  setSlideMasters: state.setSlideMasters,
@@ -9309,6 +9464,7 @@ function useContentLifecycle(input) {
9309
9464
  setTableStyleMap: state.setTableStyleMap,
9310
9465
  setThemeOptions: state.setThemeOptions,
9311
9466
  setCustomShows: state.setCustomShows,
9467
+ setActiveCustomShowId: state.setActiveCustomShowId,
9312
9468
  setSections: state.setSections,
9313
9469
  setPresentationProperties: state.setPresentationProperties,
9314
9470
  setNotesMaster: state.setNotesMaster,
@@ -9334,10 +9490,12 @@ function useContentLifecycle(input) {
9334
9490
  actionSoundHandlerRef.current = handlerRef.current;
9335
9491
  }, [handlerRef, actionSoundHandlerRef, state.loading]);
9336
9492
  useFontInjection({ embeddedFonts: state.embeddedFonts, slides });
9337
- const serializeSlides = useSerialize({
9493
+ const serializeInput = {
9338
9494
  slides,
9339
9495
  templateElementsBySlideId: state.templateElementsBySlideId,
9340
9496
  activeSlideIndex: state.activeSlideIndex,
9497
+ canvasSize: state.canvasSize,
9498
+ slideSizeEmu: state.slideSizeEmu,
9341
9499
  guides: state.guides,
9342
9500
  headerFooter: state.headerFooter,
9343
9501
  presentationProperties: state.presentationProperties,
@@ -9347,20 +9505,49 @@ function useContentLifecycle(input) {
9347
9505
  appProperties: state.appProperties,
9348
9506
  customProperties: state.customProperties,
9349
9507
  tagCollections: state.tagCollections,
9508
+ slideMasters: state.slideMasters,
9350
9509
  notesMaster: state.notesMaster,
9351
9510
  handoutMaster: state.handoutMaster,
9352
9511
  handlerRef,
9353
9512
  inlineEditingElementIdRef: state.inlineEditingElementIdRef,
9354
9513
  inlineEditingTextRef: state.inlineEditingTextRef,
9355
- password
9356
- });
9514
+ password,
9515
+ embedFonts
9516
+ };
9517
+ const serializeSlides = useSerialize(serializeInput);
9518
+ const serializeForRecovery = useSerialize({ ...serializeInput, purpose: "recovery-snapshot" });
9357
9519
  const { autosaveStatus } = useAutosave({
9358
9520
  isDirty: state.isDirty,
9359
9521
  filePath,
9360
- serializeSlides,
9361
- enabled: autosaveEnabled
9522
+ serializeSlides: serializeForRecovery,
9523
+ enabled: autosaveEnabled,
9524
+ ...autosaveIntervalSeconds === void 0 ? {} : { intervalSeconds: autosaveIntervalSeconds },
9525
+ // Everything `serializeForRecovery` reads that changes by REASSIGNMENT,
9526
+ // so a poll that finds all of them unchanged can skip re-serializing a
9527
+ // deck it has already snapshotted. The two refs are read by `.current`
9528
+ // because an inline edit in progress lives there and nowhere else yet.
9529
+ getChangeSources: () => [
9530
+ slides,
9531
+ state.templateElementsBySlideId,
9532
+ state.guides,
9533
+ state.headerFooter,
9534
+ state.presentationProperties,
9535
+ state.customShows,
9536
+ state.sections,
9537
+ state.coreProperties,
9538
+ state.appProperties,
9539
+ state.customProperties,
9540
+ state.tagCollections,
9541
+ state.notesMaster,
9542
+ state.handoutMaster,
9543
+ state.embeddedFonts,
9544
+ embedFonts,
9545
+ password,
9546
+ state.inlineEditingElementIdRef.current,
9547
+ state.inlineEditingTextRef.current
9548
+ ]
9362
9549
  });
9363
- return { handlerRef, serializeSlides, autosaveStatus };
9550
+ return { handlerRef, serializeSlides, serializeForRecovery, autosaveStatus };
9364
9551
  }
9365
9552
  function useKeyboardShortcuts(input) {
9366
9553
  const inputRef = useRef(input);
@@ -9369,7 +9556,14 @@ function useKeyboardShortcuts(input) {
9369
9556
  const current = inputRef.current;
9370
9557
  const { action, dx, dy } = mapEditorKey(e, {
9371
9558
  canEdit: current.canEdit,
9372
- isPresenting: current.mode !== "edit",
9559
+ // The Slide Master view is an editing surface, not a viewing one.
9560
+ // Gating on `mode !== 'edit'` alone made `mapEditorKey` return
9561
+ // NO_ACTION there, so Delete, the arrow-key nudges and the clipboard
9562
+ // keys were all inert over a master shape even though the write path
9563
+ // behind them routes to the owning part correctly. Svelte and
9564
+ // vanilla, which render the master into their ordinary editable
9565
+ // stage, never had the gap.
9566
+ isPresenting: current.mode !== "edit" && current.mode !== "master",
9373
9567
  hasSelection: current.hasSelection,
9374
9568
  isEditingText: Boolean(current.inlineEditingElementId || current.tableEditorState?.isEditing),
9375
9569
  isDrawing: current.activeTool !== "select",
@@ -9416,6 +9610,9 @@ function useKeyboardShortcuts(input) {
9416
9610
  case "toggleShortcuts":
9417
9611
  current.onToggleShortcuts?.();
9418
9612
  break;
9613
+ case "find":
9614
+ current.onFind?.();
9615
+ break;
9419
9616
  case "nudge":
9420
9617
  current.onNudge(dx ?? 0, dy ?? 0);
9421
9618
  break;
@@ -9524,6 +9721,7 @@ function useViewerIntegration(input) {
9524
9721
  content,
9525
9722
  filePath,
9526
9723
  autosaveEnabled,
9724
+ autosaveIntervalSeconds,
9527
9725
  canEdit,
9528
9726
  mode,
9529
9727
  slides,
@@ -9574,19 +9772,24 @@ function useViewerIntegration(input) {
9574
9772
  });
9575
9773
  const [isEncryptedDialogOpen, setIsEncryptedDialogOpen] = useState(false);
9576
9774
  const [loadVersion, setLoadVersion] = useState(0);
9577
- const { handlerRef, serializeSlides, autosaveStatus } = useContentLifecycle({
9578
- content,
9579
- filePath,
9580
- autosaveEnabled,
9581
- slides,
9582
- state,
9583
- history,
9584
- ops: editorOps.ops,
9585
- actionSoundHandlerRef,
9586
- setIsEncryptedDialogOpen,
9587
- password: dialogs.presentationPassword ?? void 0,
9588
- onContentApplied: () => setLoadVersion((v) => v + 1)
9589
- });
9775
+ const { handlerRef, serializeSlides, serializeForRecovery, autosaveStatus } = useContentLifecycle(
9776
+ {
9777
+ content,
9778
+ filePath,
9779
+ autosaveEnabled,
9780
+ autosaveIntervalSeconds,
9781
+ // The File > Fonts toggle, finally reaching a save call.
9782
+ embedFonts: dialogs.embedFontsEnabled,
9783
+ slides,
9784
+ state,
9785
+ history,
9786
+ ops: editorOps.ops,
9787
+ actionSoundHandlerRef,
9788
+ setIsEncryptedDialogOpen,
9789
+ password: dialogs.presentationPassword ?? void 0,
9790
+ onContentApplied: () => setLoadVersion((v) => v + 1)
9791
+ }
9792
+ );
9590
9793
  const { exportHandlers, printHandlers, themeHandlers, propertyHandlers } = useIOHandlers({
9591
9794
  state,
9592
9795
  slides,
@@ -9598,6 +9801,7 @@ function useViewerIntegration(input) {
9598
9801
  zoom,
9599
9802
  handlerRef,
9600
9803
  serializeSlides,
9804
+ serializeForRecovery,
9601
9805
  setContent,
9602
9806
  onContentChange,
9603
9807
  password: dialogs.presentationPassword ?? void 0
@@ -10236,11 +10440,16 @@ function useLayoutSwitching(input) {
10236
10440
  },
10237
10441
  [handler, activeSlideIndex, ops, history, onTemplateElementsChanged]
10238
10442
  );
10443
+ const loadLayoutPreviews = useCallback(
10444
+ async () => handler ? handler.getLayoutPreviews() : [],
10445
+ [handler]
10446
+ );
10239
10447
  return {
10240
10448
  availableLayouts,
10241
10449
  isLoading,
10242
10450
  loadAvailableLayouts,
10243
10451
  applyLayout,
10452
+ loadLayoutPreviews,
10244
10453
  currentLayoutPath
10245
10454
  };
10246
10455
  }
@@ -10499,41 +10708,7 @@ function buildCanvasProps(input) {
10499
10708
 
10500
10709
  // src/viewer/hooks/useScopedLayoutOptions.ts
10501
10710
  function scopeLayoutOptionsToActiveSlide(options, activeSlide) {
10502
- if (!activeSlide?.layoutPath) {
10503
- return options;
10504
- }
10505
- const hasAnyMasterInfo = options.some((o) => o.masterPath);
10506
- if (!hasAnyMasterInfo) {
10507
- return options;
10508
- }
10509
- const activeOption = options.find((o) => o.path === activeSlide.layoutPath);
10510
- const activeMaster = activeOption?.masterPath;
10511
- if (!activeMaster) {
10512
- return options;
10513
- }
10514
- const scoped = options.filter((o) => o.masterPath === activeMaster);
10515
- const seen = /* @__PURE__ */ new Map();
10516
- for (const opt of scoped) {
10517
- const isActive = opt.path === activeSlide.layoutPath;
10518
- const existing = seen.get(opt.name);
10519
- if (!existing || isActive) {
10520
- seen.set(opt.name, opt);
10521
- }
10522
- }
10523
- const chosen = new Set(Array.from(seen.values()).map((o) => o.path));
10524
- const result = [];
10525
- const usedNames = /* @__PURE__ */ new Set();
10526
- for (const opt of scoped) {
10527
- if (!chosen.has(opt.path)) {
10528
- continue;
10529
- }
10530
- if (usedNames.has(opt.name)) {
10531
- continue;
10532
- }
10533
- usedNames.add(opt.name);
10534
- result.push(opt);
10535
- }
10536
- return result;
10711
+ return scopeLayoutOptionsToSlide(options, activeSlide?.layoutPath);
10537
10712
  }
10538
10713
 
10539
10714
  // src/viewer/hooks/useViewerBuildingBlocks-toolbar-handlers.ts
@@ -10766,6 +10941,8 @@ function buildToolbarProps(input) {
10766
10941
  isOverflowMenuOpen: s.isOverflowMenuOpen,
10767
10942
  onSetOverflowMenuOpen: s.setIsOverflowMenuOpen,
10768
10943
  layoutOptions: scopedLayoutOptions,
10944
+ currentLayoutPath: activeSlide?.layoutPath,
10945
+ loadLayoutPreviews: input.loadLayoutPreviews,
10769
10946
  onInsertSlideFromLayout: slideOps.handleInsertSlideFromLayout,
10770
10947
  onInsertSlideFromTemplate: slideOps.handleInsertSlideFromTemplate,
10771
10948
  templateScheme: templateSchemeFromTheme(s.theme?.colorScheme),
@@ -10782,6 +10959,15 @@ function buildToolbarProps(input) {
10782
10959
  onToggleVersionHistory: () => propertyHandlers.setIsVersionHistoryOpen((p) => !p),
10783
10960
  onOpenPasswordProtection: () => dialogs.setIsPasswordDialogOpen(true),
10784
10961
  onOpenDocumentProperties: () => dialogs.setIsDocPropsDialogOpen(true),
10962
+ // Design > Slide Size: the size control is the inspector's SLIDE SIZE
10963
+ // card, which the deck (no-selection) panel renders. Drop the selection so
10964
+ // that panel is what the pane shows, then open it.
10965
+ onOpenSlideSize: () => {
10966
+ s.setSelectedElementId(null);
10967
+ s.setSelectedElementIds([]);
10968
+ s.setSidebarPanelMode("properties");
10969
+ s.setIsInspectorPaneOpen(true);
10970
+ },
10785
10971
  onOpenFontEmbedding: () => dialogs.setIsFontEmbeddingOpen(true),
10786
10972
  onOpenDigitalSignatures: () => dialogs.setIsDigitalSigDialogOpen(true),
10787
10973
  onEnterPresenterView,
@@ -10817,6 +11003,20 @@ function buildToolbarProps(input) {
10817
11003
  activeSlide,
10818
11004
  onTransitionChange: handleTransitionChange,
10819
11005
  onApplyTransitionToAll: handleApplyTransitionToAll,
11006
+ // No `onResetSlide` here: this building-blocks surface supplies no
11007
+ // `onApplyLayout` either, so there is no layout to reset to. The mounted
11008
+ // viewer (`ViewerToolbarSection`) wires both.
11009
+ onSelectAll: () => {
11010
+ const allIds = activeSlide?.elements.map((element) => element.id) ?? [];
11011
+ if (allIds.length > 0) {
11012
+ ops.applySelection(allIds[0], allIds);
11013
+ }
11014
+ },
11015
+ presentationProperties: s.presentationProperties,
11016
+ onPresentationPropertiesChange: (updates) => {
11017
+ s.setPresentationProperties((prev) => ({ ...prev, ...updates }));
11018
+ history.markDirty();
11019
+ },
10820
11020
  hiddenActions
10821
11021
  };
10822
11022
  }
@@ -10894,7 +11094,12 @@ function useViewerBuildingBlocksCore(input) {
10894
11094
  setMode: state.setMode,
10895
11095
  setActiveSlideIndex: state.setActiveSlideIndex,
10896
11096
  setSlides: state.setSlides,
10897
- history
11097
+ history,
11098
+ // PowerPoint's bare `J` during a show toggles live captions.
11099
+ onToggleSubtitles: () => state.setPresentationProperties((prev) => ({
11100
+ ...prev,
11101
+ showSubtitles: !prev.showSubtitles
11102
+ }))
10898
11103
  });
10899
11104
  return {
10900
11105
  state,