pptx-react-viewer 4.48.0 → 4.50.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 (123) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/dist/{AiChatPanel-NKTBHT5B.mjs → AiChatPanel-VJV5OEG2.mjs} +1 -1
  3. package/dist/AiChatPanel-VJV5OEG2.mjs.br +0 -0
  4. package/dist/AiChatPanel-VJV5OEG2.mjs.gz +0 -0
  5. package/dist/{AiChatPanel-S4WNQCY4.js → AiChatPanel-WYAL24DX.js} +9 -9
  6. package/dist/AiChatPanel-WYAL24DX.js.br +0 -0
  7. package/dist/AiChatPanel-WYAL24DX.js.gz +0 -0
  8. package/dist/{chunk-5FAX7F3O.js → chunk-4OF7X5EE.js} +1754 -3189
  9. package/dist/chunk-4OF7X5EE.js.br +0 -0
  10. package/dist/chunk-4OF7X5EE.js.gz +0 -0
  11. package/dist/{chunk-H7JRMKYC.mjs → chunk-5JJQLK7N.mjs} +542 -1977
  12. package/dist/chunk-5JJQLK7N.mjs.br +0 -0
  13. package/dist/chunk-5JJQLK7N.mjs.gz +0 -0
  14. package/dist/{chunk-PFV626ED.mjs → chunk-77IXTLKU.mjs} +3 -4
  15. package/dist/chunk-77IXTLKU.mjs.br +0 -0
  16. package/dist/chunk-77IXTLKU.mjs.gz +0 -0
  17. package/dist/{chunk-JKZNL6HP.js → chunk-D5NXYXPP.js} +773 -1245
  18. package/dist/chunk-D5NXYXPP.js.br +0 -0
  19. package/dist/chunk-D5NXYXPP.js.gz +0 -0
  20. package/dist/chunk-DDJ634MV.js.br +0 -0
  21. package/dist/chunk-DDJ634MV.js.gz +0 -0
  22. package/dist/{chunk-JAI42EXQ.js → chunk-JWNBTFZZ.js} +0 -6
  23. package/dist/chunk-JWNBTFZZ.js.br +0 -0
  24. package/dist/chunk-JWNBTFZZ.js.gz +0 -0
  25. package/dist/{chunk-JHQHAO43.mjs → chunk-QHGEPW5G.mjs} +596 -1068
  26. package/dist/chunk-QHGEPW5G.mjs.br +0 -0
  27. package/dist/chunk-QHGEPW5G.mjs.gz +0 -0
  28. package/dist/chunk-QIUK53JR.mjs.br +0 -0
  29. package/dist/chunk-QIUK53JR.mjs.gz +0 -0
  30. package/dist/i18n/de.cjs +1138 -221
  31. package/dist/i18n/de.cjs.br +0 -0
  32. package/dist/i18n/de.cjs.gz +0 -0
  33. package/dist/i18n/de.mjs +1138 -221
  34. package/dist/i18n/de.mjs.br +0 -0
  35. package/dist/i18n/de.mjs.gz +0 -0
  36. package/dist/i18n/es.cjs +1138 -221
  37. package/dist/i18n/es.cjs.br +0 -0
  38. package/dist/i18n/es.cjs.gz +0 -0
  39. package/dist/i18n/es.mjs +1138 -221
  40. package/dist/i18n/es.mjs.br +0 -0
  41. package/dist/i18n/es.mjs.gz +0 -0
  42. package/dist/i18n/fr.cjs +1138 -221
  43. package/dist/i18n/fr.cjs.br +0 -0
  44. package/dist/i18n/fr.cjs.gz +0 -0
  45. package/dist/i18n/fr.mjs +1138 -221
  46. package/dist/i18n/fr.mjs.br +0 -0
  47. package/dist/i18n/fr.mjs.gz +0 -0
  48. package/dist/i18n/zh-CN.cjs +1138 -221
  49. package/dist/i18n/zh-CN.cjs.br +0 -0
  50. package/dist/i18n/zh-CN.cjs.gz +0 -0
  51. package/dist/i18n/zh-CN.mjs +1138 -221
  52. package/dist/i18n/zh-CN.mjs.br +0 -0
  53. package/dist/i18n/zh-CN.mjs.gz +0 -0
  54. package/dist/index.d.ts +5 -15
  55. package/dist/index.js +24 -24
  56. package/dist/index.js.br +0 -0
  57. package/dist/index.js.gz +0 -0
  58. package/dist/index.mjs +3 -3
  59. package/dist/index.mjs.br +0 -0
  60. package/dist/index.mjs.gz +0 -0
  61. package/dist/internals.d.ts +105 -94
  62. package/dist/internals.js +81 -81
  63. package/dist/internals.js.br +0 -0
  64. package/dist/internals.js.gz +0 -0
  65. package/dist/internals.mjs +2 -2
  66. package/dist/internals.mjs.br +0 -0
  67. package/dist/internals.mjs.gz +0 -0
  68. package/dist/pptx-viewer-canvas.css +1 -1
  69. package/dist/pptx-viewer-canvas.css.br +0 -0
  70. package/dist/pptx-viewer-canvas.css.gz +0 -0
  71. package/dist/pptx-viewer.css +1 -1
  72. package/dist/pptx-viewer.css.br +0 -0
  73. package/dist/pptx-viewer.css.gz +0 -0
  74. package/dist/viewer/index.d.ts +7 -12
  75. package/dist/viewer/index.js +19 -19
  76. package/dist/viewer/index.js.br +0 -0
  77. package/dist/viewer/index.js.gz +0 -0
  78. package/dist/viewer/index.mjs +3 -3
  79. package/dist/viewer/index.mjs.br +0 -0
  80. package/dist/viewer/index.mjs.gz +0 -0
  81. package/dist/{y-webrtc-2H7UYAQD.mjs → y-webrtc-HRGGVQZH.mjs} +1 -1
  82. package/dist/y-webrtc-HRGGVQZH.mjs.br +0 -0
  83. package/dist/y-webrtc-HRGGVQZH.mjs.gz +0 -0
  84. package/dist/{y-webrtc-DVPQNFLL.js → y-webrtc-QVVT5PCK.js} +124 -124
  85. package/dist/y-webrtc-QVVT5PCK.js.br +0 -0
  86. package/dist/y-webrtc-QVVT5PCK.js.gz +0 -0
  87. package/dist/{y-websocket-WORVRRQS.mjs → y-websocket-46M6UFI7.mjs} +1 -1
  88. package/dist/y-websocket-46M6UFI7.mjs.br +0 -0
  89. package/dist/y-websocket-46M6UFI7.mjs.gz +0 -0
  90. package/dist/{y-websocket-TN4OKPXQ.js → y-websocket-EDOMWMAD.js} +78 -78
  91. package/dist/y-websocket-EDOMWMAD.js.br +0 -0
  92. package/dist/y-websocket-EDOMWMAD.js.gz +0 -0
  93. package/package.json +3 -3
  94. package/dist/AiChatPanel-NKTBHT5B.mjs.br +0 -0
  95. package/dist/AiChatPanel-NKTBHT5B.mjs.gz +0 -0
  96. package/dist/AiChatPanel-S4WNQCY4.js.br +0 -0
  97. package/dist/AiChatPanel-S4WNQCY4.js.gz +0 -0
  98. package/dist/chunk-3VVZGHHY.mjs.br +0 -0
  99. package/dist/chunk-3VVZGHHY.mjs.gz +0 -0
  100. package/dist/chunk-5FAX7F3O.js.br +0 -0
  101. package/dist/chunk-5FAX7F3O.js.gz +0 -0
  102. package/dist/chunk-H7JRMKYC.mjs.br +0 -0
  103. package/dist/chunk-H7JRMKYC.mjs.gz +0 -0
  104. package/dist/chunk-JAI42EXQ.js.br +0 -0
  105. package/dist/chunk-JAI42EXQ.js.gz +0 -0
  106. package/dist/chunk-JHQHAO43.mjs.br +0 -0
  107. package/dist/chunk-JHQHAO43.mjs.gz +0 -0
  108. package/dist/chunk-JKZNL6HP.js.br +0 -0
  109. package/dist/chunk-JKZNL6HP.js.gz +0 -0
  110. package/dist/chunk-NUNDT6EL.js.br +0 -0
  111. package/dist/chunk-NUNDT6EL.js.gz +0 -0
  112. package/dist/chunk-PFV626ED.mjs.br +0 -0
  113. package/dist/chunk-PFV626ED.mjs.gz +0 -0
  114. package/dist/y-webrtc-2H7UYAQD.mjs.br +0 -0
  115. package/dist/y-webrtc-2H7UYAQD.mjs.gz +0 -0
  116. package/dist/y-webrtc-DVPQNFLL.js.br +0 -0
  117. package/dist/y-webrtc-DVPQNFLL.js.gz +0 -0
  118. package/dist/y-websocket-TN4OKPXQ.js.br +0 -0
  119. package/dist/y-websocket-TN4OKPXQ.js.gz +0 -0
  120. package/dist/y-websocket-WORVRRQS.mjs.br +0 -0
  121. package/dist/y-websocket-WORVRRQS.mjs.gz +0 -0
  122. package/dist/{chunk-NUNDT6EL.js → chunk-DDJ634MV.js} +436 -436
  123. package/dist/{chunk-3VVZGHHY.mjs → chunk-QIUK53JR.mjs} +436 -436
@@ -1,7 +1,7 @@
1
- import { cloneTemplateElementsBySlideId, cloneSlide, cloneHistorySnapshot, MIN_ELEMENT_SIZE, ZOOM_TO_SELECTION_PADDING, DEFAULT_CANVAS_HEIGHT, DEFAULT_CANVAS_WIDTH, partitionTemplateElements, isTemplateElementId, DEFAULT_TABLE_ROWS, DEFAULT_TABLE_COLUMNS, comparePresentation, UNGROUPED_SECTION_ID, DEFAULT_SECTION_GROUP_ID, stopAllPersistentAudio, buildSaveSlides, MAX_ZOOM_SCALE, MIN_ZOOM_SCALE, getImageEffectsOpacity, getImageEffectsFilter, getImageRenderStyle, cn, isEditableTextElement, getShapeVisualStyle, getTextStyleForElement, DEFAULT_TEXT_COLOR, renderVectorShape, isConnectorOrLineElement, build3DExtrusionData, getImageSurfaceStyle, getTextLayoutStyle, renderTableElement, renderMediaElement, shouldRenderFallbackLabel, getElementLabel, normalizeHexColor, makeCloneId, DEFAULT_TEXT_FONT_SIZE, buildPdfFromTiledImageData, GRID_SIZE, copyFormatFromElement, hasCopyableFormat, isImageTiled, getDuotoneColors, DEFAULT_STROKE_COLOR, DEFAULT_FILL_COLOR, buildCssGradientFromShapeStyle, getCropShapeClipPath, getElementTransform, hasDagDuotoneEffect, renderDagDuotoneSvgFilter, getImageSurfaceMaskStyle, getTextWarpStyle, getTextCompensationTransform, renderTextSegments, buildReactChartViewModel, renderChartElement, buildInkGroupStrokes, getInkReplayStyles, INK_REPLAY_KEYFRAMES, buildContentPartStrokes, getContentPartReplayStyles, buildTextBody3DSceneStyle, rerouteConnectorsForMovedElements, applyReroutedConnectors, updateCellTextStyleInRawXml, updateCellTextInRawXml, splitCell, updateMergeAttrsInRawXml, mergeCells, buildNotesPdf, canvasToJpegData, applyFormatToElement, isPpactionUrl, parsePpactionUrl, getImageTilingStyle, buildCacheKey, DEFAULT_COLOR_CHANGE_TOLERANCE, getCachedResult, applyColorChange, setCachedResult, colorWithOpacity, normalizeStrokeDashType, getSvgStrokeDasharray, getCompoundLineOffsets, getCompoundLineWidths, getConnectorPathGeometry, buildLineShadowCss, buildLineGlowFilter, renderConnectorMarker, getAriaRoleDescription as getAriaRoleDescription$1, getAriaLabel as getAriaLabel$1, getAriaRole, DEFAULT_FONT_FAMILY, ACTION_BUTTON_PRESETS, isUrlSafe, computeSnapToShape, safeOpenUrl as safeOpenUrl$1, substituteFieldText, HYPERLINK_COLOR } from './chunk-PFV626ED.mjs';
2
- import { defineThreeViewElement, MIN_TOUCH_TARGET_PX, computeVirtualRange, DEFAULT_VIRTUAL_OVERSCAN, createSyncGate, clearLocalAwareness, validateRoomId, createDepartureChannel, resolveTransportForServerUrl, isMixedContentBlocked, CONNECTION_TIMEOUT_MS, registerCollaborationTeardown, createPresenceProjector, BROADCAST_THROTTLE_MS, sanitizeColor, createWheelStepBuffer, createViewerZoomStore, clampZoomScale, mapEditingWheel, createActiveAnimationGroup, cancelBuildReveal, clearPlaybackTimers, PresentationAnimationController, resolveMediaTimeNodeElementIds, resolveMediaBookmarkTimesMs, advanceMainSequence, playGroup, PRESENTATION_HASH, PRESENTATION_NONCE_KEY, PRESENTATION_CHANNEL_NAME, swapPresentationWindows, PRESENTATION_MESSAGE_ORIGIN, clearAudienceContent, buildPresentationAudienceUrl, resolveAudienceScreenPlacement, placeAudienceWindow, storeAudienceContent, isPresentationSessionMessage, endAudienceDisplay, createPresentationKeyBuffer, acceptsPresentationInput, resolveAutoAdvanceDelayMs, resolveForwardSlideWithZoomReturn, buildZoomTransitionOverride, hasShowSlideAfter, nextShowSlideIndex, previousShowSlideIndex, beginZoomExcursion, createPresenterShowGuard, firstShowSlideIndex, attachPresentationVisibilityPause, presentationEntrySlideIndex, buildMorphTransitionPlan, morphOptionToMode, buildMorphAnimationRules, findInSlides, applyFindReplacements, visibleTemplateElements, createCollaborationLivePatcher, publishLiveInlineText, initialInspectorOpen, createPresentationLoadResources, collectMediaElements, resolveMediaElementSource, collectImagePaths, applyImagePathPatches, resolveTableCellImageUrls, resolveTableStyleImageUrls, resolveTextFillBlipUrls, seedRecentColors, resolveAuthoredCustomShowId, GlyphOutlineFontCache, buildInitialGuides, readOnlyRecommendation, compatibilityWarningToasts, resolveInitialSlideIndex, resolveAutosaveIntervalMs, shouldWriteAutosaveSnapshot, saveAutosaveSnapshot, autosaveSnapshotMark, updateMasterViewElement, updateElement, masterViewElements, replaceMasterViewElements, addSection, renameSection, deleteSection, moveSectionUp, moveSectionDown, moveSlidesToSection, canMergeShapes, canInteractWithElement, prepareHtml2CanvasClone, slideProgressPercent, slideStatusLabel, EXPORT_ASSEMBLING_PERCENT, EXPORT_DONE_PERCENT, isExportAbortError, DEFAULT_VIEWER_OPTIONS, resolveImageResolutionScale, applyAcceptSlide, applyAcceptAllSlides, collectUsedFonts, describeFontEmbedding, isMobileViewport, resolveAuthoredSlideRange, mayLeaveSlideShow, buildDeckSaveOptions, resolveSlideSizeSelection, saveDeckWithPassword, shouldProbeAutosaveRecovery, probeAutosaveRecovery, acceptAutosaveRecovery, acknowledgeAutosaveRecovery, discardAutosaveRecovery, shouldLoopContinuously, applyRehearsalTimings, rasterResultToPngBlob, downloadBlob, elementPictureFilename, canEditElementPoints, selectTrustedEmbeddedFonts, resolveFontWeight, resolveFontItalic, collectReferencedFontFamilies, resolveGoogleWebfontHref, syncGoogleWebfontStylesheet, selectGoogleWebfontFamilies, fetchGoogleWebfontOutlineBytes, isFeatureEnabled, mapSlideShowStartKey, mapCustomizedEditorKey, isEditorControlTarget, isEditorTextInputTarget, parentSelection, textFontSizePtToPx, stepFontSizePt, textFontSizePxToPt, cycleSelectableElement, createCustomizationController, prepareElementForInsertion, slideSpaceElement, autosaveStatusKind, createViewerOptionsStore, observeYDocSlides, LOCAL_SYNC_ORIGIN, readSlidesFromYDoc, shouldRoomSlidesReplaceLoad, reconcileSlidesInYDoc, shouldAutoFollowBroadcaster, detectTouchDevice, detectOrientation, deriveViewportBreakpoints, createTouchGestureRecognizer, isKeyboardOpen, scopeLayoutOptionsToSlide, checkModifyPassword, viewerPreferencesFromViewProperties, DEFAULT_VIEWER_PREFERENCES, viewPropertiesPatchFromPreferences, resolveCollaborationConfig, openPptxFile, readBackstageRecentFile, resolveCollaborationShellEditability, resolveCollaborationShellState, getResizeHandleHitAreaStyle, resolveElementInteractivity, getBackgroundAnimationState, getShapeEffectLayerPlacement, buildTextStyleOverrideCss, isElementRendered, resolveElementAriaAttributes, isMediaFullscreenActive, shouldRenderHitTarget, elementHitTargetStyle, getShapeEffectAnimationLayerStyle, inlineElementPointerEvents, isHollowShapeElement, LINK_TOOLTIP_HOST_CLASS, resolvePalette as resolvePalette$1, mediaTransportVisible, oleActionsVisible, getGroupChildParentFill, deviceStrokeStageStyle, MORPH_CROSSFADE_GROUP_STYLE, MORPH_CROSSFADE_HALF_STYLE, getSlideTransitionAnimations, getFragmentedTransitionDescriptor, SLIDE_TRANSITION_KEYFRAMES, elementIdSelector, createRotationDrag, isActionHidden, buildInlineTextCommitPatch, resolveInlineEditAutoFitHeight, resolveInlineEditNormAutofitShrink, setPendingCaretPoint, drillSelectionForDoubleClick, filterInteractableIds, beginShapeAdjustment, buildElementClipboardPayload, contextMenuInspectorAnchor, savedPresentationFileName, deliverSavedPresentation, exportDeckJson, groupElements, generateElementId, ungroupElements, alignElements, distributeElements, newTableElement, createDefaultChartElement, readViewportMetrics, computeKeyboardInset, planMergeShapes, applyMergeShapesPlan, mapPresentationKey, lastShowSlideIndex, mapPresentationWheel, openPendingPrintWindow, computeColorFilter, filterHiddenSlideIndices, computeSlideIndices, buildPrintDocument, finishPrintWindow, buildPrintHtmlDocument, buildOutlineHtml, buildSlidesHtml, buildNotesHtml, buildHandoutsHtml, createBlankSlide, insertSlidePaste, applySlidePasteTemplates, makeSlideId, buildSlideTemplateSlide, templateSchemeFromTheme, needsOwnedSession, openOwnedCollaboration, resolveViewportFitOptions, editorViewportFitFor, calculateViewportFit, observeViewportClientSize, scheduleAutoAdvanceChain, computeEntranceAnimationDelay, createPresentationSessionId, resolveTransitionDurationMs, resolveTransitionSoundAction, runPresentationAction, safeOpenUrl, buildRunProgramNotice, toggleStageElementMedia, resolveOleVerbTarget, downloadDataUrl, openUrlInNewTab, createPresenterTimer, createInitialPresentationSnapshot, mergePresentationSnapshot, resetPresenterTimer, presenterElapsed, stepPresenterZoom, togglePresenterTimer, addCommentToList, removeCommentFromList, editCommentInList, toggleCommentResolvedInList, slideSpaceMembers, remapTextToSegments, getInlineEditorSelectionResult, buildInlineListStylePatch, applyActiveInlineListFormatting, applyListStyleUpdate, setPendingSelectionRestore, applyTextStyleUpdate, toggleSelectionBullets, transformInlineListCase, applyCaseTransformToSegments, transformTextCase, resolveExportCaptureDecision, recordProgressPercent, computeGridSpacingPx as computeGridSpacingPx$1, resolveShowSlideIndexes, groupSlidesBySection, masterViewPseudoSlide, overlayMasterViewInlineSnapshot, overlayInlineTextSnapshot, rasterizeElement, cloneElementForPaste, applyPasteSpecialFormat, buildRasterPictureElement, cloneElementUpdates, assertElementUpdateTarget, prepareElementUpdateBatch, createWriteBackScheduler, writeBackReport, observeExternalCollaborationReadiness, resolveHistoryDepth, createBackstagePresentation, buildFieldSubstitutionContext, getImageColorWashStyle, attachRotateHandlePlacement, getSelectionControlArtworkStyle, getSelectionOutlineColor, paintedStrokeWidth, ensureActionAffordanceStyles, resolveElementActionAffordance, actionAffordanceLabels, paintedElementSize, MEDIA_FULLSCREEN_OVERLAY_STYLE, elementContainerHeightStyle, getAnimationOpacity, resolveElementHighlightClick, applyHighlightClickStyle, getComputedEffectStyle, getSoftEdgeSvgFilter, getEffectStyleSource, getBevelLightingSvgFilter, buildStrokeOutline, buildHollowHitOutline, buildSubpathFillOverlay, strokeOutlineViewBox, resolveRevealedSmartArtNodes, resolveRevealedDrawingShapes, buildSmartArtA11y, canDrillDown, computeSmartArtElementLayout, flattenNodes, buildSummaryZoomView, readEditableText, placeCaretAt, takePendingCaretPoint, getPendingSelectionRestore, restoreSegmentSelection, mapInlineTextFormatKey, mapInlineEditorChord, subscribeBarFacePicturePixelSamples, getBarFacePicturePixelSampleVersion, ensureChartInteractionStyles, applyChartPartHighlight, resolveRevealedChartData, formatAxisValue, chartFormatTargetFromEvent, resolveOleType, getOleAriaLabel, getOleTypeColor, getOleTypeLabel, filterRenderedElements, resolveGroupChildFill, isLinkedTextBox, getOverflowSegments, shouldUseSvgWarp, getTextBodyRotationTransform, placeholderPromptDescriptor, getAriaRoleDescription, getAriaLabel, TOOLBAR_TABS, isEnterableGroup, memberChainAtPoint, findElementPath, drillSelectionForClick, scrollInspectorSectionIntoView, computeScrollDelta, replyToCommentInList, removeTableElementColumn, insertTableElementColumn, removeTableElementRow, insertTableElementRow, withCellStyle, setCellText, pickSupportedMimeType, WEBM_MIME_CANDIDATES, fpsToFrameIntervalMs, segmentFrameCount, stopCaptureStream, GIF_POST_CAPTURE_MAX_SIDE, encodeGif, rasterizeElementTiles, placeTileOnPage, PDF_SLIDES_PAGE_WIDTH_PT, PDF_SLIDES_PAGE_HEIGHT_PT, createSnapshotTextPositions, resolvePresentationAction, getImageSvgFilters, getCachedNativeImageSize, probeNativeImageSize, buildDuotoneCacheKey, getDuotoneCachedResult, applyDuotone, setDuotoneCachedResult, svgLineCap, screenStrokeWidth, connectorWrapperTransform, ACTION_INDICATOR_CLASS, ACTION_INDICATOR_ICON_PATH, LINK_TOOLTIP_CLASS, LINK_TOOLTIP_PANEL_CLASS, LINK_TOOLTIP_LABEL_CLASS, LINK_TOOLTIP_HINT_CLASS, SMARTART_DEFAULT_PALETTE, computeDrawingViewBox, projectDrawingShapes, resolveRevealedDrawingShapeNodeIds, styleShadowFilter, buildChromeStyle, measureSvgViewportRect, modelDataToBlobUrl, resolveZoomNavigationTarget, inlineListBodyText, createInlineListSeed, readListActivationSelection, createInlineListModelObserver, attachCollaborationInlineEditor, bindInlineListParagraph, bindInlineListRun, attachInlineListController, restoreInlineListBodySelection, isBulletMarkerSegment, resolveChartThreeViewSpec, applyChart3DDrag, applyChart3DSelect, findChartPartTarget, withChartTitle, resolveSmartArtThreeViewSpec, isBrowserOpenableMime, formatBytes, getOleDisplayName, getOleBadgeLabel, hasGlyphEnvelope, buildGlyphEnvelopeBlock, getWarpPath, isWedgeCalloutPresetShape, getAriaRole as getAriaRole$1, isElementActionable, buildSmartArtPresetData, classifyMediaType, publishLiveGeometry, getDraggedShapeAdjustments, computeMarqueeHitIds, mergeAdditiveSelection, rasterizeElementTiledToCanvas, borrowExternalCollaborationAwareness, observeExternalCollaborationSession, clampGifDimensions, rasterResultToPngDataUrl, shouldConfirmExternalHyperlink, centeredSvgTextLines, findSmartArtNodeText, rebuildDrawingShapesIfCleared, shouldCommitSmartArtNodeText, advanceChartMarkDrag, advanceChartValueDrag, resolveChartKind, buildChartMarkDragGeometry, beginChartMarkDrag, beginChartValueDrag, commitSmartArtNodeFill, commitSmartArtNodeText, groupIntoParagraphs as groupIntoParagraphs$1, createGlyphOutlineLookup, applyResize, lockResizeAspect, snapBoxToGrid, applyMotionPathPreset, computeInlineEditorRect, smartArtNodeAtPoint, bringForward, sendBackward, bringToFront, sendToBack } from 'ooxml-ui/pptx';
1
+ import { cloneTemplateElementsBySlideId, cloneSlide, cloneHistorySnapshot, MIN_ELEMENT_SIZE, ZOOM_TO_SELECTION_PADDING, DEFAULT_CANVAS_HEIGHT, DEFAULT_CANVAS_WIDTH, partitionTemplateElements, isTemplateElementId, DEFAULT_TABLE_ROWS, DEFAULT_TABLE_COLUMNS, comparePresentation, UNGROUPED_SECTION_ID, DEFAULT_SECTION_GROUP_ID, stopAllPersistentAudio, buildSaveSlides, MAX_ZOOM_SCALE, MIN_ZOOM_SCALE, getImageEffectsOpacity, getImageEffectsFilter, getImageRenderStyle, cn, isEditableTextElement, getShapeVisualStyle, getTextStyleForElement, DEFAULT_TEXT_COLOR, renderVectorShape, isConnectorOrLineElement, build3DExtrusionData, getImageSurfaceStyle, getTextLayoutStyle, renderTableElement, renderMediaElement, shouldRenderFallbackLabel, getElementLabel, normalizeHexColor, makeCloneId, DEFAULT_TEXT_FONT_SIZE, buildPdfFromTiledImageData, GRID_SIZE, copyFormatFromElement, hasCopyableFormat, isImageTiled, getDuotoneColors, DEFAULT_STROKE_COLOR, DEFAULT_FILL_COLOR, buildCssGradientFromShapeStyle, getCropShapeClipPath, getElementTransform, hasDagDuotoneEffect, renderDagDuotoneSvgFilter, getImageSurfaceMaskStyle, getTextWarpStyle, getTextCompensationTransform, renderTextSegments, buildReactChartViewModel, renderChartElement, buildInkGroupStrokes, getInkReplayStyles, INK_REPLAY_KEYFRAMES, buildContentPartStrokes, getContentPartReplayStyles, buildTextBody3DSceneStyle, rerouteConnectorsForMovedElements, applyReroutedConnectors, updateCellTextStyleInRawXml, updateCellTextInRawXml, splitCell, updateMergeAttrsInRawXml, mergeCells, buildNotesPdf, canvasToJpegData, applyFormatToElement, isPpactionUrl, parsePpactionUrl, getImageTilingStyle, buildCacheKey, DEFAULT_COLOR_CHANGE_TOLERANCE, getCachedResult, applyColorChange, setCachedResult, colorWithOpacity, normalizeStrokeDashType, getSvgStrokeDasharray, getCompoundLineOffsets, getCompoundLineWidths, getConnectorPathGeometry, buildLineShadowCss, buildLineGlowFilter, renderConnectorMarker, getAriaRoleDescription as getAriaRoleDescription$1, getAriaLabel as getAriaLabel$1, getAriaRole, DEFAULT_FONT_FAMILY, ACTION_BUTTON_PRESETS, isUrlSafe, safeOpenUrl as safeOpenUrl$1, substituteFieldText, HYPERLINK_COLOR } from './chunk-77IXTLKU.mjs';
2
+ import { defineThreeViewElement, MIN_TOUCH_TARGET_PX, computeVirtualRange, DEFAULT_VIRTUAL_OVERSCAN, createCollaborationSessionController, registerCollaborationTeardown, createPresenceProjector, BROADCAST_THROTTLE_MS, sanitizeColor, createEditorHistoryController, createWheelStepBuffer, createViewerZoomStore, clampZoomScale, mapEditingWheel, createActiveAnimationGroup, cancelBuildReveal, clearPlaybackTimers, PresentationAnimationController, resolveMediaTimeNodeElementIds, resolveMediaBookmarkTimesMs, advanceMainSequence, playGroup, PRESENTATION_HASH, PRESENTATION_NONCE_KEY, PRESENTATION_CHANNEL_NAME, swapPresentationWindows, PRESENTATION_MESSAGE_ORIGIN, clearAudienceContent, buildPresentationAudienceUrl, resolveAudienceScreenPlacement, placeAudienceWindow, storeAudienceContent, isPresentationSessionMessage, endAudienceDisplay, createPresentationKeyBuffer, acceptsPresentationInput, resolveAutoAdvanceDelayMs, resolveShowBackward, resolveShowForward, beginZoomExcursion, buildZoomTransitionOverride, createPresenterShowGuard, firstShowSlideIndex, attachPresentationVisibilityPause, presentationEntrySlideIndex, buildMorphTransitionPlan, morphOptionToMode, buildMorphAnimationRules, findInSlides, applyFindReplacements, visibleTemplateElements, createCollaborationLivePatcher, reportInlineTextDraft, initialInspectorOpen, createPresentationLoadResources, collectMediaElements, resolveMediaElementSource, collectImagePaths, applyImagePathPatches, resolveTableCellImageUrls, resolveTableStyleImageUrls, resolveTextFillBlipUrls, seedRecentColors, resolveAuthoredCustomShowId, GlyphOutlineFontCache, buildInitialGuides, readOnlyRecommendation, compatibilityWarningToasts, resolveInitialSlideIndex, resolveAutosaveIntervalMs, createAutosaveController, saveAutosaveSnapshot, updateMasterViewElement, updateElement, masterViewElements, replaceMasterViewElements, addSection, renameSection, deleteSection, moveSectionUp, moveSectionDown, moveSlidesToSection, canMergeShapes, canInteractWithElement, prepareHtml2CanvasClone, html2canvasCaptureScope, slideProgressPercent, slideStatusLabel, EXPORT_ASSEMBLING_PERCENT, EXPORT_DONE_PERCENT, isExportAbortError, DEFAULT_VIEWER_OPTIONS, resolveImageResolutionScale, applyAcceptSlide, applyAcceptAllSlides, collectUsedFonts, describeFontEmbedding, isMobileViewport, resolveAuthoredSlideRange, mayLeaveSlideShow, buildDeckSaveOptions, resolveSlideSizeSelection, saveDeckWithPassword, createAutosaveRecoveryController, discardAutosaveRecovery, probeAutosaveRecovery, shouldLoopContinuously, applyRehearsalTimings, rasterResultToPngBlob, downloadBlob, elementPictureFilename, canEditElementPoints, selectTrustedEmbeddedFonts, resolveFontWeight, resolveFontItalic, collectReferencedFontFamilies, resolveGoogleWebfontHref, syncGoogleWebfontStylesheet, selectGoogleWebfontFamilies, fetchGoogleWebfontOutlineBytes, dispatchEditorKey, isFeatureEnabled, parentSelection, textFontSizePtToPx, stepFontSizePt, textFontSizePxToPt, cycleSelectableElement, createCustomizationController, prepareElementForInsertion, slideSpaceElement, autosaveStatusKind, createViewerOptionsStore, observeYDocSlides, LOCAL_SYNC_ORIGIN, readSlidesFromYDoc, shouldRoomSlidesReplaceLoad, reconcileSlidesInYDoc, shouldAutoFollowBroadcaster, detectTouchDevice, detectOrientation, deriveViewportBreakpoints, createTouchGestureRecognizer, isKeyboardOpen, scopeLayoutOptionsToSlide, INITIAL_READ_ONLY_SESSION, readOnlySessionActions, applyReadOnlySessionEvent, readOnlyBannerVisible, readOnlyLocked, viewerPreferencesFromViewProperties, DEFAULT_VIEWER_PREFERENCES, viewPropertiesPatchFromPreferences, resolveCollaborationConfig, openPptxFile, readBackstageRecentFile, resolveCollaborationShellEditability, resolveCollaborationShellState, getResizeHandleHitAreaStyle, resolveElementInteractivity, getBackgroundAnimationState, getShapeEffectLayerPlacement, buildTextStyleOverrideCss, isElementRendered, resolveElementAriaAttributes, isMediaFullscreenActive, shouldRenderHitTarget, elementHitTargetStyle, getShapeEffectAnimationLayerStyle, inlineElementPointerEvents, isHollowShapeElement, LINK_TOOLTIP_HOST_CLASS, resolvePalette as resolvePalette$1, mediaTransportVisible, oleActionsVisible, getGroupChildParentFill, deviceStrokeStageStyle, MORPH_CROSSFADE_GROUP_STYLE, MORPH_CROSSFADE_HALF_STYLE, getSlideTransitionAnimations, getFragmentedTransitionDescriptor, SLIDE_TRANSITION_KEYFRAMES, isActionHidden, buildInlineTextCommitPatch, resolveInlineEditAutoFitHeight, resolveInlineEditNormAutofitShrink, setPendingCaretPoint, resolveGroupDoubleClickTarget, filterInteractableIds, beginShapeAdjustment, buildElementClipboardPayload, contextMenuInspectorAnchor, savedPresentationFileName, deliverSavedPresentation, exportDeckJson, mapCustomizedEditorKey, isEditorTextInputTarget, groupElements, generateElementId, ungroupElements, alignElements, distributeElements, newTableElement, createDefaultChartElement, readViewportMetrics, computeKeyboardInset, planMergeShapes, applyMergeShapesPlan, mapPresentationKey, lastShowSlideIndex, mapPresentationWheel, openPendingPrintWindow, computeColorFilter, filterHiddenSlideIndices, computeSlideIndices, buildPrintDocument, finishPrintWindow, buildPrintHtmlDocument, buildOutlineHtml, buildSlidesHtml, buildNotesHtml, buildHandoutsHtml, createBlankSlide, insertSlidePaste, applySlidePasteTemplates, makeSlideId, buildSlideTemplateSlide, templateSchemeFromTheme, needsOwnedSession, openOwnedCollaboration, resolveViewportFitOptions, editorViewportFitFor, calculateViewportFit, observeViewportClientSize, scheduleAutoAdvanceChain, computeEntranceAnimationDelay, createPresentationSessionId, resolveTransitionDurationMs, resolveTransitionSoundAction, runPresentationAction, safeOpenUrl, buildRunProgramNotice, toggleStageElementMedia, resolveOleVerbTarget, downloadDataUrl, openUrlInNewTab, createPresenterTimer, createInitialPresentationSnapshot, mergePresentationSnapshot, resetPresenterTimer, presenterElapsed, stepPresenterZoom, togglePresenterTimer, addCommentToList, removeCommentFromList, editCommentInList, toggleCommentResolvedInList, slideSpaceMembers, remapTextToSegments, getInlineEditorSelectionResult, buildInlineListStylePatch, applyActiveInlineListFormatting, applyListStyleUpdate, setPendingSelectionRestore, applyTextStyleUpdate, toggleSelectionBullets, transformInlineListCase, applyCaseTransformToSegments, transformTextCase, createGestureController, resolveExportCaptureDecision, recordProgressPercent, computeGridSpacingPx as computeGridSpacingPx$1, resolveShowSlideIndexes, groupSlidesBySection, masterViewPseudoSlide, overlayMasterViewInlineSnapshot, overlayInlineTextSnapshot, rasterizeElement, cloneElementForPaste, applyPasteSpecialFormat, buildRasterPictureElement, cloneElementUpdates, assertElementUpdateTarget, prepareElementUpdateBatch, createWriteBackScheduler, writeBackReport, createSyncGate, observeExternalCollaborationReadiness, resolveHistoryDepth, createBackstagePresentation, buildFieldSubstitutionContext, getImageColorWashStyle, attachRotateHandlePlacement, getSelectionControlArtworkStyle, getSelectionOutlineColor, paintedStrokeWidth, ensureActionAffordanceStyles, resolveElementActionAffordance, actionAffordanceLabels, paintedElementSize, MEDIA_FULLSCREEN_OVERLAY_STYLE, elementContainerHeightStyle, getAnimationOpacity, resolveElementHighlightClick, applyHighlightClickStyle, getComputedEffectStyle, getSoftEdgeSvgFilter, getEffectStyleSource, getBevelLightingSvgFilter, buildStrokeOutline, buildHollowHitOutline, buildSubpathFillOverlay, strokeOutlineViewBox, buildSummaryZoomView, readEditableText, placeCaretAt, takePendingCaretPoint, getPendingSelectionRestore, restoreSegmentSelection, mapInlineTextFormatKey, mapInlineEditorChord, subscribeBarFacePicturePixelSamples, getBarFacePicturePixelSampleVersion, ensureChartInteractionStyles, applyChartPartHighlight, resolveRevealedChartData, formatAxisValue, chartFormatTargetFromEvent, resolveOleType, getOleAriaLabel, getOleTypeColor, getOleTypeLabel, filterRenderedElements, resolveGroupChildFill, isLinkedTextBox, getOverflowSegments, shouldUseSvgWarp, getTextBodyRotationTransform, placeholderPromptDescriptor, getAriaRoleDescription, getAriaLabel, elementIdSelector, TOOLBAR_TABS, resolveGroupPressTarget, scrollInspectorSectionIntoView, computeScrollDelta, replyToCommentInList, removeTableElementColumn, insertTableElementColumn, removeTableElementRow, insertTableElementRow, withCellStyle, setCellText, pickSupportedMimeType, WEBM_MIME_CANDIDATES, fpsToFrameIntervalMs, segmentFrameCount, stopCaptureStream, GIF_POST_CAPTURE_MAX_SIDE, encodeGif, rasterizeElementTiles, placeTileOnPage, PDF_SLIDES_PAGE_WIDTH_PT, PDF_SLIDES_PAGE_HEIGHT_PT, createSnapshotTextPositions, resolvePresentationAction, getImageSvgFilters, getCachedNativeImageSize, probeNativeImageSize, buildDuotoneCacheKey, getDuotoneCachedResult, applyDuotone, setDuotoneCachedResult, svgLineCap, screenStrokeWidth, connectorWrapperTransform, ACTION_INDICATOR_CLASS, ACTION_INDICATOR_ICON_PATH, LINK_TOOLTIP_CLASS, LINK_TOOLTIP_PANEL_CLASS, LINK_TOOLTIP_LABEL_CLASS, LINK_TOOLTIP_HINT_CLASS, modelDataToBlobUrl, resolveZoomNavigationTarget, inlineListBodyText, createInlineListSeed, readListActivationSelection, createInlineListModelObserver, attachCollaborationInlineEditor, bindInlineListParagraph, bindInlineListRun, attachInlineListController, restoreInlineListBodySelection, isBulletMarkerSegment, resolveChartThreeViewSpec, applyChart3DDrag, applyChart3DSelect, canDrillDown, findChartPartTarget, withChartTitle, resolveSmartArtThreeViewSpec, measureSvgViewportRect, isBrowserOpenableMime, formatBytes, getOleDisplayName, getOleBadgeLabel, hasGlyphEnvelope, buildGlyphEnvelopeBlock, getWarpPath, isWedgeCalloutPresetShape, getAriaRole as getAriaRole$1, isElementActionable, buildSmartArtPresetData, classifyMediaType, getDraggedShapeAdjustments, computeMarqueeHitIds, mergeAdditiveSelection, rasterizeElementTiledToCanvas, borrowExternalCollaborationAwareness, observeExternalCollaborationSession, publishLiveGeometry, clampGifDimensions, rasterResultToPngDataUrl, shouldConfirmExternalHyperlink, advanceChartMarkDrag, advanceChartValueDrag, resolveChartKind, buildChartMarkDragGeometry, beginChartMarkDrag, beginChartValueDrag, findSmartArtNodeText, commitSmartArtNodeFill, commitSmartArtNodeText, groupIntoParagraphs as groupIntoParagraphs$1, createGlyphOutlineLookup, applyMotionPathPreset, resolveRevealedSmartArtNodes, resolveRevealedDrawingShapes, buildSmartArtA11y, computeSmartArtElementLayout, flattenNodes, computeInlineEditorRect, smartArtNodeAtPoint, SMARTART_DEFAULT_PALETTE, computeDrawingViewBox, projectDrawingShapes, resolveRevealedDrawingShapeNodeIds, styleShadowFilter, buildChromeStyle, centeredSvgTextLines, rebuildDrawingShapesIfCleared, shouldCommitSmartArtNodeText, bringForward, sendBackward, bringToFront, sendToBack } from 'ooxml-ui/pptx';
3
3
  export { DUOTONE_PRESETS, MOBILE_BREAKPOINT, SLIDE_TRANSITION_KEYFRAMES, TABLET_BREAKPOINT, clearAudienceContent, computeVirtualRange, downloadBlob, getSlideTransitionAnimations, loadAudienceContent, storeAudienceContent } from 'ooxml-ui/pptx';
4
- import React17, { useRef, useState, useCallback, useEffect, useMemo, useContext, useLayoutEffect, createContext, useImperativeHandle, useSyncExternalStore, Suspense } from 'react';
4
+ import React17, { useRef, useState, useCallback, useEffect, useSyncExternalStore, useMemo, useContext, useLayoutEffect, createContext, useImperativeHandle, Suspense } from 'react';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
6
  import { PptxHandler, EncryptedFileError, hasShapeProperties, applyThemeToData, THEME_PRESETS, themeColorSchemesEqual, guidePxToEmu, hasTextProperties, isInkElement, duplicateSlide, buildThemeColorMap, reResolveSlideColors, reResolveElementColors, isImageLikeElement, SvgExporter, reResolveElementFonts, getSubstituteFontFamily, elementActionToPptxAction, setSmartArtNodeStyle, updateSmartArtNodeText } from 'ooxml-core/pptx';
7
7
  import { translationsEn } from 'ooxml-ui/pptx/i18n';
@@ -75,240 +75,59 @@ function useVirtualizedSlides({
75
75
  scrollToIndex
76
76
  };
77
77
  }
78
+ function useController(controller) {
79
+ return useSyncExternalStore(controller.subscribe, controller.getSnapshot, controller.getSnapshot);
80
+ }
81
+
82
+ // src/viewer/hooks/collaboration/collaboration-modules.ts
83
+ var collabModules = {
84
+ loadYjs: () => import('yjs'),
85
+ loadWebsocket: () => import('./y-websocket-46M6UFI7.mjs'),
86
+ loadWebrtc: () => import('./y-webrtc-HRGGVQZH.mjs')
87
+ };
88
+
89
+ // src/viewer/hooks/collaboration/useYjsProvider.ts
78
90
  function useYjsProvider({ config: inputConfig }) {
79
91
  const config = inputConfig?.externalSession ? void 0 : inputConfig;
80
- const [status, setStatus] = useState("disconnected");
81
- const [awareness, setAwareness] = useState(null);
82
- const [doc, setDoc] = useState(null);
83
- const [clientId, setClientId] = useState(null);
84
- const [synced, setSynced] = useState(false);
85
- const cleanupRef = useRef(null);
86
- const generationRef = useRef(0);
87
- const timeoutRef = useRef(null);
88
- const departureRef = useRef(null);
89
- const gateRef = useRef(null);
90
- if (!gateRef.current) {
91
- gateRef.current = createSyncGate(() => setSynced(true));
92
- }
93
- const teardown = useCallback(() => {
94
- generationRef.current += 1;
95
- if (timeoutRef.current) {
96
- clearTimeout(timeoutRef.current);
97
- timeoutRef.current = null;
98
- }
99
- gateRef.current?.reset();
100
- setSynced(false);
101
- cleanupRef.current?.();
102
- cleanupRef.current = null;
103
- }, []);
104
- const announceDeparture = useCallback(() => {
105
- departureRef.current?.announce();
106
- departureRef.current?.dispose();
107
- departureRef.current = null;
108
- }, []);
109
- const buildCleanup = useCallback(
110
- (provider, yDoc) => () => {
111
- announceDeparture();
112
- clearLocalAwareness(provider.awareness);
113
- provider.destroy();
114
- yDoc.destroy();
115
- setDoc(null);
116
- setAwareness(null);
117
- setClientId(null);
118
- setStatus("disconnected");
119
- },
120
- [announceDeparture]
92
+ const [controller] = useState(
93
+ () => createCollaborationSessionController({ modules: collabModules, registerTeardown: false })
121
94
  );
122
- const initWebrtc = useCallback(async () => {
123
- const generation = generationRef.current;
124
- if (!config) {
125
- return;
126
- }
127
- setStatus("connecting");
128
- try {
129
- const [Y, { WebrtcProvider }] = await Promise.all([import('yjs'), import('./y-webrtc-2H7UYAQD.mjs')]);
130
- if (generation !== generationRef.current) {
131
- return;
132
- }
133
- const yDoc = new Y.Doc();
134
- const opts = {};
135
- if (config.signaling && config.signaling.length > 0) {
136
- opts.signaling = config.signaling;
137
- }
138
- if (config.authToken) {
139
- opts.password = config.authToken;
140
- }
141
- const roomId = validateRoomId(config.roomId);
142
- const provider = new WebrtcProvider(roomId, yDoc, opts);
143
- departureRef.current = createDepartureChannel(roomId, provider.awareness);
144
- setDoc(yDoc);
145
- setAwareness(provider.awareness);
146
- setClientId(provider.awareness.clientID);
147
- setStatus("connected");
148
- const handleSynced = (event) => {
149
- if (event?.synced !== false) {
150
- gateRef.current?.open();
151
- }
152
- };
153
- provider.on("synced", handleSynced);
154
- gateRef.current?.arm();
155
- const handleStatus = (event) => {
156
- if (event.connected === false) {
157
- setStatus("disconnected");
158
- gateRef.current?.reset();
159
- setSynced(false);
160
- gateRef.current?.arm();
161
- } else if (event.connected) {
162
- setStatus("connected");
163
- }
164
- };
165
- provider.on("status", handleStatus);
166
- const baseCleanup = buildCleanup(provider, yDoc);
167
- cleanupRef.current = () => {
168
- provider.off("synced", handleSynced);
169
- provider.off("status", handleStatus);
170
- baseCleanup();
171
- };
172
- } catch (err) {
173
- if (generation !== generationRef.current) {
174
- return;
175
- }
176
- console.warn(
177
- "[pptx-viewer] WebRTC collaboration packages not available:",
178
- err instanceof Error ? err.message : err
179
- );
180
- setStatus("error");
181
- }
182
- }, [config?.roomId, config?.authToken, config?.signaling, buildCleanup]);
183
- const init = useCallback(async () => {
184
- teardown();
185
- const generation = generationRef.current;
95
+ const state = useController(controller);
96
+ const signalingKey = config?.signaling?.join("\n");
97
+ useEffect(() => {
186
98
  if (!config) {
99
+ controller.stop();
187
100
  return;
188
101
  }
189
- const roomId = validateRoomId(config.roomId);
190
- const transport = config.transport ?? resolveTransportForServerUrl(config.serverUrl);
191
- if (transport === "webrtc") {
192
- await initWebrtc();
193
- return;
194
- }
195
- if (isMixedContentBlocked(config.serverUrl)) {
196
- console.warn(
197
- `[pptx-viewer] Refusing to connect: insecure ws:// server "${config.serverUrl}" is blocked from a secure (https) page. Use a wss:// URL.`
198
- );
199
- setStatus("error");
200
- return;
201
- }
202
- setStatus("connecting");
203
- try {
204
- const [Y, { WebsocketProvider }] = await Promise.all([import('yjs'), import('./y-websocket-WORVRRQS.mjs')]);
205
- if (generation !== generationRef.current) {
206
- return;
207
- }
208
- const yDoc = new Y.Doc();
209
- const provider = new WebsocketProvider(config.serverUrl, roomId, yDoc, {
210
- params: config.authToken ? { token: config.authToken } : void 0
211
- });
212
- departureRef.current = createDepartureChannel(roomId, provider.awareness);
213
- let connected = false;
214
- const handleStatus = (event) => {
215
- if (event.status === "connected") {
216
- connected = true;
217
- if (timeoutRef.current) {
218
- clearTimeout(timeoutRef.current);
219
- timeoutRef.current = null;
220
- }
221
- setStatus("connected");
222
- gateRef.current?.arm();
223
- } else if (event.status === "disconnected") {
224
- setStatus("disconnected");
225
- gateRef.current?.reset();
226
- setSynced(false);
227
- gateRef.current?.arm();
228
- }
229
- };
230
- const handleSynced = (isSynced) => {
231
- if (isSynced) {
232
- gateRef.current?.open();
233
- }
234
- };
235
- provider.on("status", handleStatus);
236
- provider.on("sync", handleSynced);
237
- if (provider.wsconnected) {
238
- connected = true;
239
- setStatus("connected");
240
- gateRef.current?.arm();
241
- }
242
- if (provider.synced) {
243
- gateRef.current?.open();
244
- }
245
- if (!connected) {
246
- timeoutRef.current = setTimeout(() => {
247
- timeoutRef.current = null;
248
- if (!connected) {
249
- provider.off("status", handleStatus);
250
- provider.off("sync", handleSynced);
251
- announceDeparture();
252
- clearLocalAwareness(provider.awareness);
253
- provider.destroy();
254
- yDoc.destroy();
255
- setDoc(null);
256
- setAwareness(null);
257
- setClientId(null);
258
- cleanupRef.current = null;
259
- setStatus("error");
260
- }
261
- }, CONNECTION_TIMEOUT_MS);
262
- }
263
- setDoc(yDoc);
264
- setAwareness(provider.awareness);
265
- setClientId(provider.awareness.clientID);
266
- cleanupRef.current = () => {
267
- provider.off("status", handleStatus);
268
- provider.off("sync", handleSynced);
269
- announceDeparture();
270
- clearLocalAwareness(provider.awareness);
271
- provider.destroy();
272
- yDoc.destroy();
273
- setDoc(null);
274
- setAwareness(null);
275
- setClientId(null);
276
- setStatus("disconnected");
277
- };
278
- } catch (err) {
279
- if (generation !== generationRef.current) {
280
- return;
281
- }
282
- console.warn(
283
- "[pptx-viewer] Collaboration packages not available:",
284
- err instanceof Error ? err.message : err
285
- );
286
- setStatus("error");
287
- }
102
+ void controller.start(config);
103
+ const disposeTeardown = registerCollaborationTeardown({
104
+ leave: () => controller.stop(),
105
+ rejoin: () => void controller.retry()
106
+ });
107
+ return () => {
108
+ disposeTeardown();
109
+ controller.stop();
110
+ };
288
111
  }, [
112
+ controller,
289
113
  config?.roomId,
290
114
  config?.serverUrl,
291
115
  config?.authToken,
292
116
  config?.transport,
293
- announceDeparture,
294
- initWebrtc,
295
- teardown
117
+ signalingKey,
118
+ Boolean(config)
296
119
  ]);
297
- useEffect(() => {
298
- init();
299
- const disposeTeardown = registerCollaborationTeardown({
300
- leave: teardown,
301
- rejoin: init
302
- });
303
- return () => {
304
- disposeTeardown();
305
- teardown();
306
- };
307
- }, [init, teardown]);
308
- const retry = useCallback(() => {
309
- init();
310
- }, [init]);
311
- return { status, awareness, doc, clientId, synced, retry };
120
+ const session = state.active ? controller.getSession() : null;
121
+ return {
122
+ status: state.status,
123
+ awareness: session?.awareness ?? null,
124
+ doc: session?.doc ?? null,
125
+ clientId: state.clientId,
126
+ synced: state.synced,
127
+ retry: () => {
128
+ void controller.retry();
129
+ }
130
+ };
312
131
  }
313
132
  function usePresenceTracking({
314
133
  awareness,
@@ -757,15 +576,11 @@ function serializeHistoryDocument(snapshot) {
757
576
  )
758
577
  });
759
578
  }
760
- function recordExplicitSlideUpdate(past, future, previous, before, label, maxDepth) {
579
+ function recordExplicitSlideUpdate(history, previous, before, label) {
761
580
  if (previous && serializeHistoryDocument(previous) !== serializeHistoryDocument(before)) {
762
- past.push(cloneHistorySnapshot(previous));
763
- }
764
- past.push({ ...cloneHistorySnapshot(before), actionLabel: label });
765
- while (past.length > Math.max(1, maxDepth)) {
766
- past.shift();
581
+ history.record(cloneHistorySnapshot(previous), previous.actionLabel);
767
582
  }
768
- future.length = 0;
583
+ history.record({ ...cloneHistorySnapshot(before), actionLabel: label }, label);
769
584
  }
770
585
  var POLL_INTERVAL_MS = 16;
771
586
  function usePointerReleaseWakeup(hasActivePointerInteraction) {
@@ -801,7 +616,6 @@ function usePointerReleaseWakeup(hasActivePointerInteraction) {
801
616
  }
802
617
 
803
618
  // src/viewer/hooks/useEditorHistory.ts
804
- var DEFAULT_MAX_HISTORY_ENTRIES = 120;
805
619
  function useEditorHistory(input) {
806
620
  const {
807
621
  slides,
@@ -810,7 +624,7 @@ function useEditorHistory(input) {
810
624
  templateElementsBySlideId,
811
625
  loading,
812
626
  error,
813
- maxHistoryEntries = DEFAULT_MAX_HISTORY_ENTRIES,
627
+ maxHistoryEntries,
814
628
  hasActivePointerInteraction,
815
629
  pointerCommitNonce,
816
630
  onDirty,
@@ -821,30 +635,23 @@ function useEditorHistory(input) {
821
635
  setSelectedElementId,
822
636
  setSelectedElementIds
823
637
  } = input;
824
- const historyPastRef = useRef([]);
825
- const historyFutureRef = useRef([]);
638
+ const [history] = useState(() => createEditorHistoryController());
639
+ const historyState = useController(history);
826
640
  const lastHistorySnapshotRef = useRef(null);
827
641
  const lastHistorySerializedRef = useRef("");
828
642
  const lastCheapHashRef = useRef("");
829
643
  const isApplyingHistoryRef = useRef(false);
830
644
  const trackingHeldRef = useRef(false);
831
645
  const unlockHistoryTimerRef = useRef(null);
832
- const [canUndo, setCanUndo] = useState(false);
833
- const [canRedo, setCanRedo] = useState(false);
834
- const [undoLabel, setUndoLabel] = useState(void 0);
835
- const [redoLabel, setRedoLabel] = useState(void 0);
836
646
  const [editCommitNonce, setEditCommitNonce] = useState(0);
837
647
  const { pointerReleaseNonce, deferUntilPointerReleased } = usePointerReleaseWakeup(
838
648
  hasActivePointerInteraction
839
649
  );
840
- const updateHistoryAvailability = useCallback(() => {
841
- setCanUndo(historyPastRef.current.length > 0);
842
- setCanRedo(historyFutureRef.current.length > 0);
843
- const pastTop = historyPastRef.current[historyPastRef.current.length - 1];
844
- setUndoLabel(pastTop?.actionLabel);
845
- const futureTop = historyFutureRef.current[historyFutureRef.current.length - 1];
846
- setRedoLabel(futureTop?.actionLabel);
847
- }, []);
650
+ useEffect(() => {
651
+ if (maxHistoryEntries !== void 0) {
652
+ history.setMaxDepth(maxHistoryEntries);
653
+ }
654
+ }, [history, maxHistoryEntries]);
848
655
  const buildHistorySnapshot = useCallback(
849
656
  (actionLabel) => {
850
657
  return {
@@ -899,53 +706,40 @@ function useEditorHistory(input) {
899
706
  onDirtyRef.current?.();
900
707
  }, []);
901
708
  const handleUndo = useCallback(() => {
902
- const previousSnapshot = historyPastRef.current.pop();
903
- if (!previousSnapshot) {
709
+ if (!history.getSnapshot().canUndo) {
710
+ return;
711
+ }
712
+ const result = history.undo(buildHistorySnapshot());
713
+ if (!result) {
904
714
  return;
905
715
  }
906
- const currentSnapshot = buildHistorySnapshot(previousSnapshot.actionLabel);
907
- historyFutureRef.current.push(currentSnapshot);
908
716
  isApplyingHistoryRef.current = true;
909
- const nextSnapshot = cloneHistorySnapshot(previousSnapshot);
717
+ const nextSnapshot = cloneHistorySnapshot(result.snapshot);
910
718
  lastHistorySnapshotRef.current = cloneHistorySnapshot(nextSnapshot);
911
719
  lastHistorySerializedRef.current = serializeHistoryDocument(nextSnapshot);
912
720
  applyHistorySnapshot(nextSnapshot);
913
- updateHistoryAvailability();
914
721
  unlockHistoryTracking();
915
722
  markDirty();
916
- }, [
917
- applyHistorySnapshot,
918
- buildHistorySnapshot,
919
- markDirty,
920
- unlockHistoryTracking,
921
- updateHistoryAvailability
922
- ]);
723
+ }, [applyHistorySnapshot, buildHistorySnapshot, history, markDirty, unlockHistoryTracking]);
923
724
  const handleRedo = useCallback(() => {
924
- const nextSnapshot = historyFutureRef.current.pop();
925
- if (!nextSnapshot) {
725
+ if (!history.getSnapshot().canRedo) {
726
+ return;
727
+ }
728
+ const result = history.redo(buildHistorySnapshot());
729
+ if (!result) {
926
730
  return;
927
731
  }
928
- const currentSnapshot = buildHistorySnapshot(nextSnapshot.actionLabel);
929
- historyPastRef.current.push(currentSnapshot);
930
732
  isApplyingHistoryRef.current = true;
931
- const targetSnapshot = cloneHistorySnapshot(nextSnapshot);
733
+ const targetSnapshot = cloneHistorySnapshot(result.snapshot);
932
734
  lastHistorySnapshotRef.current = cloneHistorySnapshot(targetSnapshot);
933
735
  lastHistorySerializedRef.current = serializeHistoryDocument(targetSnapshot);
934
736
  applyHistorySnapshot(targetSnapshot);
935
- updateHistoryAvailability();
936
737
  unlockHistoryTracking();
937
738
  markDirty();
938
- }, [
939
- applyHistorySnapshot,
940
- buildHistorySnapshot,
941
- markDirty,
942
- unlockHistoryTracking,
943
- updateHistoryAvailability
944
- ]);
739
+ }, [applyHistorySnapshot, buildHistorySnapshot, history, markDirty, unlockHistoryTracking]);
945
740
  const resetHistory = useCallback(
946
741
  (initialSnapshot) => {
947
- historyPastRef.current = [];
948
- historyFutureRef.current = [];
742
+ history.clear();
949
743
  if (initialSnapshot) {
950
744
  const clonedInitial = cloneHistorySnapshot(initialSnapshot);
951
745
  lastHistorySnapshotRef.current = clonedInitial;
@@ -954,9 +748,8 @@ function useEditorHistory(input) {
954
748
  lastHistorySnapshotRef.current = null;
955
749
  lastHistorySerializedRef.current = "";
956
750
  }
957
- updateHistoryAvailability();
958
751
  },
959
- [updateHistoryAvailability]
752
+ [history]
960
753
  );
961
754
  useEffect(() => {
962
755
  return () => {
@@ -999,19 +792,13 @@ function useEditorHistory(input) {
999
792
  lastHistorySnapshotRef.current = snapshot;
1000
793
  lastHistorySerializedRef.current = serialized;
1001
794
  lastCheapHashRef.current = cheapHash;
1002
- updateHistoryAvailability();
1003
795
  return;
1004
796
  }
1005
797
  onDirtyRef.current?.();
1006
- historyPastRef.current.push(previousSnapshot);
1007
- while (historyPastRef.current.length > Math.max(1, maxHistoryEntries)) {
1008
- historyPastRef.current.shift();
1009
- }
1010
- historyFutureRef.current = [];
798
+ history.record(previousSnapshot, previousSnapshot.actionLabel);
1011
799
  lastHistorySnapshotRef.current = snapshot;
1012
800
  lastHistorySerializedRef.current = serialized;
1013
801
  lastCheapHashRef.current = cheapHash;
1014
- updateHistoryAvailability();
1015
802
  }, [
1016
803
  activeSlideIndex,
1017
804
  buildHistorySnapshot,
@@ -1021,30 +808,21 @@ function useEditorHistory(input) {
1021
808
  editCommitNonce,
1022
809
  error,
1023
810
  hasActivePointerInteraction,
811
+ history,
1024
812
  loading,
1025
- maxHistoryEntries,
1026
813
  pointerCommitNonce,
1027
814
  pointerReleaseNonce,
1028
- slides,
1029
- updateHistoryAvailability
815
+ slides
1030
816
  ]);
1031
817
  const commitSlides = (nextSlides, label, beforeSlides = slides) => {
1032
818
  const before = { ...buildHistorySnapshot(), slides: beforeSlides.map(cloneSlide) };
1033
819
  const after = { ...before, slides: nextSlides.map(cloneSlide) };
1034
- recordExplicitSlideUpdate(
1035
- historyPastRef.current,
1036
- historyFutureRef.current,
1037
- lastHistorySnapshotRef.current,
1038
- before,
1039
- label,
1040
- maxHistoryEntries
1041
- );
820
+ recordExplicitSlideUpdate(history, lastHistorySnapshotRef.current, before, label);
1042
821
  lastHistorySnapshotRef.current = cloneHistorySnapshot(after);
1043
822
  lastHistorySerializedRef.current = serializeHistoryDocument(after);
1044
823
  isApplyingHistoryRef.current = false;
1045
824
  setSlides(nextSlides);
1046
825
  onDirtyRef.current?.();
1047
- updateHistoryAvailability();
1048
826
  };
1049
827
  const holdTracking = useCallback((held) => {
1050
828
  trackingHeldRef.current = held;
@@ -1052,10 +830,10 @@ function useEditorHistory(input) {
1052
830
  return {
1053
831
  commitSlides,
1054
832
  holdTracking,
1055
- canUndo,
1056
- canRedo,
1057
- undoLabel,
1058
- redoLabel,
833
+ canUndo: historyState.canUndo,
834
+ canRedo: historyState.canRedo,
835
+ undoLabel: historyState.undoLabel,
836
+ redoLabel: historyState.redoLabel,
1059
837
  handleUndo,
1060
838
  handleRedo,
1061
839
  resetHistory,
@@ -2198,9 +1976,6 @@ function executeSlideTransition(nextSlideIndex, deps) {
2198
1976
  }
2199
1977
 
2200
1978
  // src/viewer/hooks/presentation-mode/useSlideNavigation.ts
2201
- function isClickAdvanceBlocked(slide, direction, trigger) {
2202
- return trigger === "click" && direction === 1 && slide?.transition?.advanceOnClick === false;
2203
- }
2204
1979
  function useSlideNavigation(input) {
2205
1980
  const {
2206
1981
  slides,
@@ -2250,50 +2025,71 @@ function useSlideNavigation(input) {
2250
2025
  );
2251
2026
  const movePresentationSlide = useCallback(
2252
2027
  (direction, trigger = "explicit") => {
2253
- if (direction === 1 && playNextAnimationGroup()) {
2254
- return;
2255
- }
2256
- if (isClickAdvanceBlocked(slides[presentationSlideIndex], direction, trigger)) {
2028
+ const showOrder = visibleSlideIndexes.length > 0 ? visibleSlideIndexes : slides.map((_slide, slideIndex) => slideIndex);
2029
+ const current = presentationSlideIndex;
2030
+ const transitionTo = (index, options = {}) => executeSlideTransition(index, {
2031
+ slides,
2032
+ currentSlideIndex: current,
2033
+ onPlayActionSound,
2034
+ onStopActionSound,
2035
+ setPresentationSlideVisible,
2036
+ clearPresentationTimers,
2037
+ setPresentationSlideIndex,
2038
+ onSetActiveSlideIndex,
2039
+ seedSlideAnimations,
2040
+ startSlideAnimations,
2041
+ scheduleAutoAdvanceForSlide: rehearsing ? void 0 : scheduleAutoAdvanceForSlide,
2042
+ presentationTimersRef,
2043
+ setTransitionOverlay,
2044
+ // PowerPoint plays a slide's transition only when advancing into it,
2045
+ // and replays the LEAVING slide's transition in reverse when stepping
2046
+ // back (a morph glides its shapes back to where they came from).
2047
+ playTransition: true,
2048
+ ...options
2049
+ });
2050
+ if (direction === -1) {
2051
+ const step2 = resolveShowBackward({
2052
+ currentIndex: current,
2053
+ showOrder,
2054
+ endOfShow: false,
2055
+ seededCompleted: false
2056
+ });
2057
+ if (step2.kind === "go") {
2058
+ transitionTo(step2.index, { reverse: true, seedCompleted: true });
2059
+ }
2257
2060
  return;
2258
2061
  }
2259
- if (direction === 1) {
2260
- const consumedExcursion = zoomExcursionRef.current;
2261
- const step = resolveForwardSlideWithZoomReturn(
2262
- presentationSlideIndex,
2263
- void 0,
2264
- consumedExcursion
2265
- );
2266
- if (step.returnedToZoom && step.nextSlideIndex !== void 0) {
2267
- zoomExcursionRef.current = step.excursion;
2268
- executeSlideTransition(step.nextSlideIndex, {
2269
- slides,
2270
- currentSlideIndex: presentationSlideIndex,
2271
- onPlayActionSound,
2272
- onStopActionSound,
2273
- setPresentationSlideVisible,
2274
- clearPresentationTimers,
2275
- setPresentationSlideIndex,
2276
- onSetActiveSlideIndex,
2277
- seedSlideAnimations,
2278
- startSlideAnimations,
2062
+ const step = resolveShowForward({
2063
+ currentIndex: current,
2064
+ currentSlide: slides[current],
2065
+ showOrder,
2066
+ endOfShow: false,
2067
+ fromClick: trigger === "click",
2068
+ loop: Boolean(loopContinuously) && !rehearsing,
2069
+ endWithBlackSlide: true,
2070
+ zoomExcursion: zoomExcursionRef.current,
2071
+ advanceBuild: playNextAnimationGroup
2072
+ });
2073
+ if (step.kind === "go") {
2074
+ if (step.zoomReturn) {
2075
+ zoomExcursionRef.current = step.zoomReturn.excursion;
2076
+ transitionTo(step.index, {
2279
2077
  scheduleAutoAdvanceForSlide,
2280
- presentationTimersRef,
2281
- setTransitionOverlay,
2282
- playTransition: true,
2283
- transitionOverride: buildZoomTransitionOverride(
2284
- consumedExcursion?.transitionDurationMs
2285
- )
2078
+ transitionOverride: step.zoomReturn.transitionOverride
2286
2079
  });
2287
2080
  return;
2288
2081
  }
2082
+ if (rehearsing) {
2083
+ recordCurrentSlideTime(current);
2084
+ }
2085
+ transitionTo(step.index);
2086
+ return;
2289
2087
  }
2290
- const availableSlideIndexes = visibleSlideIndexes.length > 0 ? visibleSlideIndexes : slides.map((_slide, slideIndex) => slideIndex);
2291
- if (availableSlideIndexes.length === 0) {
2088
+ if (step.kind !== "end-screen" && step.kind !== "exit") {
2292
2089
  return;
2293
2090
  }
2294
- const pastLastSlide = direction === 1 && !hasShowSlideAfter(presentationSlideIndex, availableSlideIndexes);
2295
- if (rehearsing && pastLastSlide) {
2296
- recordCurrentSlideTime(presentationSlideIndex);
2091
+ if (rehearsing) {
2092
+ recordCurrentSlideTime(current);
2297
2093
  try {
2298
2094
  if (document.fullscreenElement) {
2299
2095
  void document.exitFullscreen().catch(() => {
@@ -2305,41 +2101,7 @@ function useSlideNavigation(input) {
2305
2101
  setShowRehearsalSummary(true);
2306
2102
  return;
2307
2103
  }
2308
- if (!loopContinuously && !rehearsing && pastLastSlide && onAdvancePastLastSlide) {
2309
- onAdvancePastLastSlide();
2310
- return;
2311
- }
2312
- const nextSlideIndex = direction === 1 ? nextShowSlideIndex(presentationSlideIndex, availableSlideIndexes, {
2313
- loop: Boolean(loopContinuously) && !rehearsing
2314
- }) : previousShowSlideIndex(presentationSlideIndex, availableSlideIndexes);
2315
- if (nextSlideIndex === void 0 || nextSlideIndex === presentationSlideIndex) {
2316
- return;
2317
- }
2318
- if (rehearsing && direction === 1) {
2319
- recordCurrentSlideTime(presentationSlideIndex);
2320
- }
2321
- executeSlideTransition(nextSlideIndex, {
2322
- slides,
2323
- currentSlideIndex: presentationSlideIndex,
2324
- onPlayActionSound,
2325
- onStopActionSound,
2326
- setPresentationSlideVisible,
2327
- clearPresentationTimers,
2328
- setPresentationSlideIndex,
2329
- onSetActiveSlideIndex,
2330
- seedSlideAnimations,
2331
- startSlideAnimations,
2332
- scheduleAutoAdvanceForSlide: rehearsing ? void 0 : scheduleAutoAdvanceForSlide,
2333
- presentationTimersRef,
2334
- setTransitionOverlay,
2335
- // PowerPoint plays a slide's transition only when advancing into it,
2336
- // and replays the LEAVING slide's transition in reverse when stepping
2337
- // back (a morph glides its shapes back to where they came from).
2338
- playTransition: true,
2339
- reverse: direction === -1,
2340
- // Stepping back onto a slide shows it with its builds already played.
2341
- seedCompleted: direction === -1
2342
- });
2104
+ onAdvancePastLastSlide?.();
2343
2105
  },
2344
2106
  [
2345
2107
  clearPresentationTimers,
@@ -4199,6 +3961,7 @@ function useViewerCoreState(_input) {
4199
3961
  const marqueeStateRef = useRef(null);
4200
3962
  const isDrawingRef = useRef(false);
4201
3963
  const justInteractedRef = useRef(false);
3964
+ const gestureControllerRef = useRef(null);
4202
3965
  const livePatcher = useMemo(() => createCollaborationLivePatcher(), []);
4203
3966
  useEffect(() => () => livePatcher.dispose(), [livePatcher]);
4204
3967
  const [mode, setMode] = useState("edit");
@@ -4215,10 +3978,11 @@ function useViewerCoreState(_input) {
4215
3978
  const [selectedElementId, setSelectedElementId] = useState(null);
4216
3979
  const [selectedElementIds, setSelectedElementIds] = useState([]);
4217
3980
  const [isDirty, setIsDirty] = useState(false);
3981
+ const autosaveDraftSinkRef = useRef(null);
4218
3982
  const publishInlineText = useCallback(
4219
3983
  (elementId, text) => {
4220
- publishLiveInlineText(
4221
- livePatcher,
3984
+ reportInlineTextDraft(
3985
+ { livePatcher, autosave: autosaveDraftSinkRef.current },
4222
3986
  slidesRef.current[activeSlideIndexRef.current],
4223
3987
  elementId,
4224
3988
  text
@@ -4284,6 +4048,7 @@ function useViewerCoreState(_input) {
4284
4048
  imageInputRef,
4285
4049
  mediaInputRef,
4286
4050
  activeSlideIndexRef,
4051
+ autosaveDraftSinkRef,
4287
4052
  ...inlineEditing,
4288
4053
  ...editPointsMode,
4289
4054
  dragStateRef,
@@ -4292,6 +4057,7 @@ function useViewerCoreState(_input) {
4292
4057
  marqueeStateRef,
4293
4058
  isDrawingRef,
4294
4059
  justInteractedRef,
4060
+ gestureControllerRef,
4295
4061
  livePatcher,
4296
4062
  mode,
4297
4063
  setMode,
@@ -4778,96 +4544,43 @@ function useLoadContent({
4778
4544
  return { handlerRef };
4779
4545
  }
4780
4546
  function useAutosave(input) {
4781
- const { isDirty, filePath, serializeSlides, enabled = true, getChangeSources } = input;
4547
+ const { isDirty, filePath, enabled = true } = input;
4782
4548
  const intervalMs = resolveAutosaveIntervalMs({
4783
4549
  hostIntervalMs: input.intervalMs,
4784
4550
  optionsIntervalSeconds: input.intervalSeconds
4785
4551
  });
4786
- const [autosaveStatus, setAutosaveStatus] = useState({
4787
- state: "idle"
4788
- });
4789
- const isDirtyRef = useRef(isDirty);
4790
- const filePathRef = useRef(filePath);
4791
- const serializeRef = useRef(serializeSlides);
4792
- const storageRef = useRef(input.storage);
4793
- storageRef.current = input.storage;
4794
- const isSavingRef = useRef(false);
4795
- const changeSourcesRef = useRef(getChangeSources);
4796
- const lastSnapshotRef = useRef(void 0);
4797
- const onSavedRef = useRef(input.onSaved);
4798
- useEffect(() => {
4799
- onSavedRef.current = input.onSaved;
4800
- }, [input.onSaved]);
4801
- useEffect(() => {
4802
- isDirtyRef.current = isDirty;
4803
- }, [isDirty]);
4804
- useEffect(() => {
4805
- filePathRef.current = filePath;
4806
- }, [filePath]);
4807
- useEffect(() => {
4808
- serializeRef.current = serializeSlides;
4809
- }, [serializeSlides]);
4552
+ const live = useRef(input);
4553
+ live.current = input;
4554
+ const [controller] = useState(
4555
+ () => createAutosaveController({
4556
+ getActivation: () => ({ active: live.current.enabled ?? true }),
4557
+ getFilePath: () => live.current.filePath,
4558
+ getIntervalMs: () => resolveAutosaveIntervalMs({
4559
+ hostIntervalMs: live.current.intervalMs,
4560
+ optionsIntervalSeconds: live.current.intervalSeconds
4561
+ }),
4562
+ serialize: () => live.current.serializeSlides(),
4563
+ getStorage: () => live.current.storage,
4564
+ onSaved: (data) => live.current.onSaved?.(data),
4565
+ writeSnapshot: saveAutosaveSnapshot,
4566
+ // serializeSlides already overlays the live inline draft from its refs.
4567
+ getInlineEditingId: () => live.current.getInlineEditingId?.()
4568
+ })
4569
+ );
4570
+ if (input.inlineDraftSinkRef) {
4571
+ input.inlineDraftSinkRef.current = controller;
4572
+ }
4573
+ const { status } = useController(controller);
4810
4574
  useEffect(() => {
4811
- changeSourcesRef.current = getChangeSources;
4812
- }, [getChangeSources]);
4813
- const doAutosave = useCallback(async (options) => {
4814
- const path = filePathRef.current;
4815
- if (!path) {
4816
- return;
4817
- }
4818
- const sources = changeSourcesRef.current?.() ?? [];
4819
- if (!shouldWriteAutosaveSnapshot({
4820
- filePath: path,
4821
- isDirty: isDirtyRef.current,
4822
- saving: isSavingRef.current,
4823
- sources: options?.polled === true ? sources : [],
4824
- lastSnapshot: lastSnapshotRef.current
4825
- })) {
4826
- return;
4827
- }
4828
- isSavingRef.current = true;
4829
- setAutosaveStatus({ state: "saving" });
4830
- try {
4831
- const data = await serializeRef.current();
4832
- if (!data) {
4833
- setAutosaveStatus({ state: "idle" });
4834
- isSavingRef.current = false;
4835
- return;
4836
- }
4837
- await saveAutosaveSnapshot(path, data, storageRef.current);
4838
- lastSnapshotRef.current = autosaveSnapshotMark(path, sources);
4839
- setAutosaveStatus({ state: "saved", timestamp: Date.now() });
4840
- onSavedRef.current?.(data);
4841
- } catch (err) {
4842
- setAutosaveStatus({
4843
- state: "error",
4844
- message: err instanceof Error ? err.message : "Autosave failed"
4845
- });
4846
- } finally {
4847
- isSavingRef.current = false;
4848
- }
4849
- }, []);
4575
+ controller.refresh();
4576
+ }, [controller, enabled, filePath, intervalMs]);
4850
4577
  useEffect(() => {
4851
- if (!enabled) {
4852
- setAutosaveStatus({ state: "disabled", reason: "autosave_toggle_off" });
4853
- return;
4854
- }
4855
- if (!filePath) {
4856
- setAutosaveStatus({
4857
- state: "disabled",
4858
- reason: "no_file_path"
4859
- });
4860
- return;
4861
- }
4862
- setAutosaveStatus((prev) => prev.state === "disabled" ? { state: "idle" } : prev);
4863
- const id = setInterval(() => {
4864
- void doAutosave({ polled: true });
4865
- }, intervalMs);
4866
- return () => clearInterval(id);
4867
- }, [enabled, filePath, intervalMs, doAutosave]);
4578
+ controller.observe(isDirty, live.current.getChangeSources?.() ?? []);
4579
+ });
4580
+ useEffect(() => () => controller.markClean(), [controller]);
4868
4581
  return {
4869
- autosaveStatus,
4870
- triggerAutosave: () => doAutosave()
4582
+ autosaveStatus: status,
4583
+ triggerAutosave: () => controller.saveNow()
4871
4584
  };
4872
4585
  }
4873
4586
 
@@ -5382,45 +5095,238 @@ function syncSelectionHandleOverlay(elementNode, elementId, geometry) {
5382
5095
  }
5383
5096
  }
5384
5097
 
5385
- // src/viewer/hooks/pointer-move-handlers.ts
5386
- function computeResizeGeometry(handle, startX, startY, startWidth, startHeight, dx, dy, snapToGrid, gridSpacingPx, lockAspect = false) {
5387
- const startBox = { x: startX, y: startY, width: startWidth, height: startHeight };
5388
- let resized = applyResize(startBox, handle, dx, dy, 1, { minSize: MIN_ELEMENT_SIZE });
5389
- if (lockAspect) {
5390
- resized = lockResizeAspect(resized, startBox, handle, MIN_ELEMENT_SIZE);
5098
+ // src/viewer/hooks/canvas-gestures.ts
5099
+ var animationFrames = {
5100
+ request: (callback) => requestAnimationFrame(callback),
5101
+ cancel: (handle) => cancelAnimationFrame(handle)
5102
+ };
5103
+ function createCanvasGestures(getInput, frames = animationFrames) {
5104
+ let armed = null;
5105
+ let pending = null;
5106
+ let frameHandle = 0;
5107
+ let lastSnapLinesKey = "";
5108
+ const liveTarget = () => {
5109
+ const { livePatcher, editTemplateMode, activeSlide } = getInput();
5110
+ return livePatcher && !editTemplateMode && livePatcher.isActive() ? { patcher: livePatcher, slideId: activeSlide?.id } : void 0;
5111
+ };
5112
+ function showSnapLines(lines) {
5113
+ const next = lines.map((line) => ({
5114
+ axis: line.axis === "v" ? "x" : "y",
5115
+ position: line.position
5116
+ }));
5117
+ const key = JSON.stringify(next);
5118
+ if (key !== lastSnapLinesKey) {
5119
+ lastSnapLinesKey = key;
5120
+ getInput().setSnapLines(next);
5121
+ }
5391
5122
  }
5392
- const box = { x: resized.x, y: resized.y, width: resized.width, height: resized.height };
5393
- if (!snapToGrid) {
5394
- return box;
5123
+ function paintMove(drag, transform) {
5124
+ const start = drag.startPositionsById[transform.id];
5125
+ if (!start) {
5126
+ return;
5127
+ }
5128
+ drag.lastDx = transform.x - start.x;
5129
+ drag.lastDy = transform.y - start.y;
5130
+ const live = liveTarget();
5131
+ for (const [id, from] of Object.entries(drag.startPositionsById)) {
5132
+ const x = from.x + drag.lastDx;
5133
+ const y = from.y + drag.lastDy;
5134
+ const domEl = drag.domEls.get(id);
5135
+ if (domEl) {
5136
+ domEl.style.left = `${x}px`;
5137
+ domEl.style.top = `${y}px`;
5138
+ syncSelectionHandleOverlay(domEl, id, { x, y });
5139
+ }
5140
+ if (live) {
5141
+ publishLiveGeometry(live.patcher, live.slideId, id, { x, y });
5142
+ }
5143
+ }
5144
+ }
5145
+ function paintResize(resize, transform) {
5146
+ const { x, y } = transform;
5147
+ const width = Math.max(transform.width, MIN_ELEMENT_SIZE);
5148
+ const height = Math.max(transform.height, MIN_ELEMENT_SIZE);
5149
+ resize.lastX = x;
5150
+ resize.lastY = y;
5151
+ resize.lastWidth = transform.width;
5152
+ resize.lastHeight = transform.height;
5153
+ if (resize.domEl) {
5154
+ Object.assign(resize.domEl.style, {
5155
+ left: `${x}px`,
5156
+ top: `${y}px`,
5157
+ width: `${width}px`,
5158
+ height: `${height}px`
5159
+ });
5160
+ syncSelectionHandleOverlay(resize.domEl, resize.elementId, { x, y, width, height });
5161
+ }
5162
+ const live = liveTarget();
5163
+ if (live) {
5164
+ publishLiveGeometry(live.patcher, live.slideId, resize.elementId, { x, y, width, height });
5165
+ }
5166
+ }
5167
+ function paintPending() {
5168
+ const frame = pending;
5169
+ pending = null;
5170
+ frameHandle = 0;
5171
+ if (!frame || !armed) {
5172
+ return;
5173
+ }
5174
+ if (armed.kind === "move") {
5175
+ armed.drag.moved = true;
5176
+ showSnapLines(frame.lines);
5177
+ paintMove(armed.drag, frame.transform);
5178
+ } else {
5179
+ armed.resize.moved = true;
5180
+ paintResize(armed.resize, frame.transform);
5181
+ }
5182
+ }
5183
+ function flushFrame() {
5184
+ if (frameHandle !== 0) {
5185
+ frames.cancel(frameHandle);
5186
+ }
5187
+ paintPending();
5395
5188
  }
5396
- return snapBoxToGrid(box, handle, gridSpacingPx, MIN_ELEMENT_SIZE);
5189
+ const controller = createGestureController({
5190
+ getScale: () => getInput().editorScale,
5191
+ // Axis-aligned, as the canvas has always moved and resized (no rotation).
5192
+ getElementBox: (id) => {
5193
+ const el = getInput().elementLookup.get(id);
5194
+ return el ? { x: el.x, y: el.y, width: el.width, height: el.height } : void 0;
5195
+ },
5196
+ getSiblings: () => {
5197
+ const { editTemplateMode, templateElements, activeSlide } = getInput();
5198
+ const source = editTemplateMode ? templateElements : activeSlide?.elements ?? [];
5199
+ return source.map(({ id, x, y, width, height }) => ({ id, x, y, width, height }));
5200
+ },
5201
+ getSnapToGrid: () => getInput().snapToGrid,
5202
+ getGridSize: () => getInput().gridSpacingPx,
5203
+ getSnapToShape: () => getInput().snapToShape,
5204
+ getGuides: () => getInput().guides.map(({ axis, position }) => ({ axis, position })),
5205
+ getMinSize: () => MIN_ELEMENT_SIZE,
5206
+ getSnapExcludeIds: () => armed?.kind === "move" ? Object.keys(armed.drag.startPositionsById) : [],
5207
+ getStageOrigin: () => {
5208
+ const rect = getInput().canvasStageRef.current?.getBoundingClientRect();
5209
+ return { left: rect?.left ?? 0, top: rect?.top ?? 0 };
5210
+ },
5211
+ onStart: () => void 0,
5212
+ onPreview: (transform, lines) => {
5213
+ pending = { transform, lines };
5214
+ if (frameHandle === 0) {
5215
+ frameHandle = frames.request(paintPending);
5216
+ }
5217
+ },
5218
+ onEnd: (transform, moved) => {
5219
+ flushFrame();
5220
+ const ended = armed;
5221
+ armed = null;
5222
+ if (ended) {
5223
+ finishGesture(ended, moved ? transform : null);
5224
+ }
5225
+ }
5226
+ });
5227
+ function finishGesture(ended, transform) {
5228
+ const input = getInput();
5229
+ if (transform) {
5230
+ if (ended.kind === "move") {
5231
+ commitMove(input, ended.drag);
5232
+ } else {
5233
+ commitResize(input, ended.resize);
5234
+ }
5235
+ input.justInteractedRef.current = true;
5236
+ input.livePatcher?.flush();
5237
+ }
5238
+ input.dragStateRef.current = null;
5239
+ input.resizeStateRef.current = null;
5240
+ lastSnapLinesKey = "";
5241
+ input.setSnapLines([]);
5242
+ if (transform) {
5243
+ input.markDirty();
5244
+ }
5245
+ input.setPointerCommitNonce((n) => n + 1);
5246
+ }
5247
+ return {
5248
+ begin(kind, id, pointer) {
5249
+ if (controller.isActive()) {
5250
+ return;
5251
+ }
5252
+ const input = getInput();
5253
+ const drag = input.dragStateRef.current;
5254
+ const resize = input.resizeStateRef.current;
5255
+ armed = kind === "move" && drag ? { kind, drag } : kind === "resize" && resize ? { kind, resize } : null;
5256
+ if (!armed) {
5257
+ return;
5258
+ }
5259
+ lastSnapLinesKey = "";
5260
+ const handle = armed.kind === "resize" ? armed.resize.handle : void 0;
5261
+ controller.begin(kind, id, pointer, handle);
5262
+ if (!controller.isActive()) {
5263
+ armed = null;
5264
+ }
5265
+ },
5266
+ isActive: () => controller.isActive(),
5267
+ dispose: () => {
5268
+ if (frameHandle !== 0) {
5269
+ frames.cancel(frameHandle);
5270
+ frameHandle = 0;
5271
+ }
5272
+ pending = null;
5273
+ armed = null;
5274
+ controller.dispose();
5275
+ }
5276
+ };
5397
5277
  }
5398
- function resolveLiveTarget(input) {
5399
- const { livePatcher, editTemplateMode, activeSlide } = input;
5400
- if (!livePatcher || editTemplateMode || !livePatcher.isActive()) {
5401
- return void 0;
5278
+ function commitMove(input, drag) {
5279
+ const { lastDx: dx, lastDy: dy } = drag;
5280
+ const movedIds = new Set(Object.keys(drag.startPositionsById));
5281
+ const moveElements = (elements) => {
5282
+ const moved = elements.map((el) => {
5283
+ const start = drag.startPositionsById[el.id];
5284
+ return start ? { ...el, x: start.x + dx, y: start.y + dy } : el;
5285
+ });
5286
+ return applyReroutedConnectors(moved, rerouteConnectorsForMovedElements(moved, movedIds));
5287
+ };
5288
+ if (input.editTemplateMode) {
5289
+ const slideId = input.activeSlide?.id;
5290
+ if (slideId) {
5291
+ input.setTemplateElementsBySlideId((prev) => ({
5292
+ ...prev,
5293
+ [slideId]: moveElements(prev[slideId] ?? [])
5294
+ }));
5295
+ }
5296
+ return;
5402
5297
  }
5403
- return { patcher: livePatcher, slideId: activeSlide?.id };
5298
+ input.updateSlides(
5299
+ (prev) => prev.map(
5300
+ (s, i) => i !== input.activeSlideIndex ? s : { ...s, elements: moveElements(s.elements) }
5301
+ )
5302
+ );
5303
+ }
5304
+ function commitResize(input, resize) {
5305
+ input.updateElementById(resize.elementId, {
5306
+ x: resize.lastX,
5307
+ y: resize.lastY,
5308
+ width: resize.lastWidth,
5309
+ height: resize.lastHeight
5310
+ });
5311
+ const movedIds = /* @__PURE__ */ new Set([resize.elementId]);
5312
+ input.updateSlides(
5313
+ (prev) => prev.map((s, i) => {
5314
+ if (i !== input.activeSlideIndex) {
5315
+ return s;
5316
+ }
5317
+ const rerouted = rerouteConnectorsForMovedElements(s.elements, movedIds);
5318
+ return rerouted.length === 0 ? s : { ...s, elements: applyReroutedConnectors(s.elements, rerouted) };
5319
+ })
5320
+ );
5404
5321
  }
5405
- function processPointerMove(e, input, tracker) {
5322
+ function processPointerMove(e, input) {
5406
5323
  const {
5407
5324
  editorScale,
5408
5325
  canvasStageRef,
5409
5326
  canvasSize,
5410
- snapToGrid,
5411
- snapToShape,
5412
- gridSpacingPx,
5413
- editTemplateMode,
5414
- templateElements,
5415
- activeSlide,
5416
- guides,
5417
- elementLookup,
5418
5327
  marqueeStateRef,
5419
- dragStateRef,
5420
- resizeStateRef,
5421
5328
  shapeAdjustmentDragStateRef,
5422
5329
  setMarqueeSelectionState,
5423
- setSnapLines,
5424
5330
  updateElementById
5425
5331
  } = input;
5426
5332
  const marquee = marqueeStateRef.current;
@@ -5435,31 +5341,6 @@ function processPointerMove(e, input, tracker) {
5435
5341
  );
5436
5342
  return;
5437
5343
  }
5438
- const drag = dragStateRef.current;
5439
- if (drag) {
5440
- processDragMove(
5441
- e,
5442
- drag,
5443
- editorScale,
5444
- snapToGrid,
5445
- snapToShape,
5446
- gridSpacingPx,
5447
- editTemplateMode,
5448
- templateElements,
5449
- activeSlide,
5450
- guides,
5451
- elementLookup,
5452
- tracker,
5453
- setSnapLines,
5454
- resolveLiveTarget(input)
5455
- );
5456
- return;
5457
- }
5458
- const rs = resizeStateRef.current;
5459
- if (rs) {
5460
- processResizeMove(e, rs, editorScale, snapToGrid, gridSpacingPx, resolveLiveTarget(input));
5461
- return;
5462
- }
5463
5344
  const adj = shapeAdjustmentDragStateRef.current;
5464
5345
  if (adj) {
5465
5346
  processAdjustmentMove(e, adj, editorScale, updateElementById);
@@ -5475,129 +5356,6 @@ function processMarqueeMove(e, marquee, canvasStageRef, canvasSize, editorScale,
5475
5356
  marquee.currentY = Math.max(0, Math.min(canvasSize.height, (e.clientY - rect.top) / editorScale));
5476
5357
  setMarqueeSelectionState({ ...marquee });
5477
5358
  }
5478
- function processDragMove(e, drag, editorScale, snapToGrid, snapToShape, gridSpacingPx, editTemplateMode, templateElements, activeSlide, guides, elementLookup, tracker, setSnapLines, live) {
5479
- const dx = (e.clientX - drag.startClientX) / editorScale;
5480
- const dy = (e.clientY - drag.startClientY) / editorScale;
5481
- if (!drag.moved && (Math.abs(dx) > 2 || Math.abs(dy) > 2)) {
5482
- drag.moved = true;
5483
- }
5484
- if (!drag.moved) {
5485
- return;
5486
- }
5487
- let appliedDx = dx;
5488
- let appliedDy = dy;
5489
- const draggedIds = Object.keys(drag.startPositionsById);
5490
- const primaryId = draggedIds[0];
5491
- const primaryStart = primaryId ? drag.startPositionsById[primaryId] : void 0;
5492
- const primaryEl = primaryId ? elementLookup.get(primaryId) : void 0;
5493
- if (primaryStart && primaryEl) {
5494
- let targetX = primaryStart.x + appliedDx;
5495
- let targetY = primaryStart.y + appliedDy;
5496
- if (snapToGrid) {
5497
- targetX = Math.round(targetX / gridSpacingPx) * gridSpacingPx;
5498
- targetY = Math.round(targetY / gridSpacingPx) * gridSpacingPx;
5499
- }
5500
- if (snapToShape) {
5501
- const siblingSource = editTemplateMode ? templateElements : activeSlide?.elements ?? [];
5502
- const siblings = siblingSource.map((el) => ({
5503
- id: el.id,
5504
- x: el.x,
5505
- y: el.y,
5506
- width: el.width,
5507
- height: el.height
5508
- }));
5509
- const snapResult = computeSnapToShape(
5510
- targetX,
5511
- targetY,
5512
- primaryEl.width,
5513
- primaryEl.height,
5514
- siblings,
5515
- new Set(draggedIds),
5516
- guides.map((g) => ({ axis: g.axis, position: g.position }))
5517
- );
5518
- targetX = snapResult.x;
5519
- targetY = snapResult.y;
5520
- const newSnapLines = snapResult.lines.map((line) => ({
5521
- axis: line.axis === "v" ? "x" : "y",
5522
- position: line.position
5523
- }));
5524
- const newKey = JSON.stringify(newSnapLines);
5525
- if (newKey !== tracker.lastSnapLinesKey) {
5526
- tracker.lastSnapLinesKey = newKey;
5527
- setSnapLines(newSnapLines);
5528
- }
5529
- } else if (tracker.lastSnapLinesKey !== "[]") {
5530
- tracker.lastSnapLinesKey = "[]";
5531
- setSnapLines([]);
5532
- }
5533
- appliedDx = targetX - primaryStart.x;
5534
- appliedDy = targetY - primaryStart.y;
5535
- }
5536
- drag.lastDx = appliedDx;
5537
- drag.lastDy = appliedDy;
5538
- for (const [id, domEl] of drag.domEls) {
5539
- const start = drag.startPositionsById[id];
5540
- if (start) {
5541
- const x = start.x + appliedDx;
5542
- const y = start.y + appliedDy;
5543
- domEl.style.left = `${x}px`;
5544
- domEl.style.top = `${y}px`;
5545
- syncSelectionHandleOverlay(domEl, id, { x, y });
5546
- }
5547
- }
5548
- if (live) {
5549
- for (const id of Object.keys(drag.startPositionsById)) {
5550
- const start = drag.startPositionsById[id];
5551
- publishLiveGeometry(live.patcher, live.slideId, id, {
5552
- x: start.x + appliedDx,
5553
- y: start.y + appliedDy
5554
- });
5555
- }
5556
- }
5557
- }
5558
- function processResizeMove(e, rs, editorScale, snapToGrid, gridSpacingPx, live) {
5559
- const dx = (e.clientX - rs.startClientX) / editorScale;
5560
- const dy = (e.clientY - rs.startClientY) / editorScale;
5561
- if (!rs.moved && (Math.abs(dx) > 2 || Math.abs(dy) > 2)) {
5562
- rs.moved = true;
5563
- }
5564
- if (!rs.moved) {
5565
- return;
5566
- }
5567
- const geo = computeResizeGeometry(
5568
- rs.handle,
5569
- rs.startX,
5570
- rs.startY,
5571
- rs.startWidth,
5572
- rs.startHeight,
5573
- dx,
5574
- dy,
5575
- snapToGrid,
5576
- gridSpacingPx,
5577
- e.shiftKey
5578
- );
5579
- rs.lastX = geo.x;
5580
- rs.lastY = geo.y;
5581
- rs.lastWidth = geo.width;
5582
- rs.lastHeight = geo.height;
5583
- const width = Math.max(geo.width, MIN_ELEMENT_SIZE);
5584
- const height = Math.max(geo.height, MIN_ELEMENT_SIZE);
5585
- if (rs.domEl) {
5586
- rs.domEl.style.left = `${geo.x}px`;
5587
- rs.domEl.style.top = `${geo.y}px`;
5588
- rs.domEl.style.width = `${width}px`;
5589
- rs.domEl.style.height = `${height}px`;
5590
- syncSelectionHandleOverlay(rs.domEl, rs.elementId, { x: geo.x, y: geo.y, width, height });
5591
- }
5592
- if (live) {
5593
- publishLiveGeometry(live.patcher, live.slideId, rs.elementId, {
5594
- x: geo.x,
5595
- y: geo.y,
5596
- width,
5597
- height
5598
- });
5599
- }
5600
- }
5601
5359
  function processAdjustmentMove(e, adj, editorScale, updateElementById) {
5602
5360
  const dx = (e.clientX - adj.startClientX) / editorScale;
5603
5361
  const dy = (e.clientY - adj.startClientY) / editorScale;
@@ -5616,26 +5374,20 @@ function processPointerUp(input) {
5616
5374
  editTemplateMode,
5617
5375
  templateElements,
5618
5376
  activeSlide,
5619
- activeSlideIndex,
5620
5377
  marqueeStateRef,
5621
5378
  dragStateRef,
5622
5379
  resizeStateRef,
5623
5380
  shapeAdjustmentDragStateRef,
5624
5381
  justInteractedRef,
5382
+ gestureRef,
5625
5383
  setMarqueeSelectionState,
5626
- setSnapLines,
5627
- setTemplateElementsBySlideId,
5628
5384
  setPointerCommitNonce,
5629
5385
  applySelection,
5630
5386
  clearSelection,
5631
- updateSlides,
5632
- updateElementById,
5633
5387
  markDirty,
5634
5388
  livePatcher
5635
5389
  } = input;
5636
5390
  const marquee = marqueeStateRef.current;
5637
- const drag = dragStateRef.current;
5638
- const rs = resizeStateRef.current;
5639
5391
  const adj = shapeAdjustmentDragStateRef.current;
5640
5392
  if (marquee) {
5641
5393
  commitMarquee(
@@ -5646,62 +5398,22 @@ function processPointerUp(input) {
5646
5398
  applySelection,
5647
5399
  clearSelection
5648
5400
  );
5649
- marqueeStateRef.current = null;
5650
- setMarqueeSelectionState(null);
5651
5401
  }
5652
- if (drag?.moved) {
5653
- commitDrag(
5654
- drag,
5655
- editTemplateMode,
5656
- activeSlide,
5657
- activeSlideIndex,
5658
- setTemplateElementsBySlideId,
5659
- updateSlides
5660
- );
5402
+ if (!gestureRef?.current?.isActive()) {
5403
+ dragStateRef.current = null;
5404
+ resizeStateRef.current = null;
5661
5405
  }
5662
- if (rs?.moved) {
5663
- const resizedId = rs.elementId;
5664
- const resizeUpdates = {
5665
- x: rs.lastX,
5666
- y: rs.lastY,
5667
- width: rs.lastWidth,
5668
- height: rs.lastHeight
5669
- };
5670
- updateElementById(resizedId, resizeUpdates);
5671
- const movedIds = /* @__PURE__ */ new Set([resizedId]);
5672
- updateSlides(
5673
- (prev) => prev.map((s, i) => {
5674
- if (i !== activeSlideIndex) {
5675
- return s;
5676
- }
5677
- const rerouted = rerouteConnectorsForMovedElements(s.elements, movedIds);
5678
- if (rerouted.length === 0) {
5679
- return s;
5680
- }
5681
- return {
5682
- ...s,
5683
- elements: applyReroutedConnectors(s.elements, rerouted)
5684
- };
5685
- })
5686
- );
5687
- }
5688
- const wasMoved = drag?.moved || rs?.moved || adj?.moved;
5689
- if (wasMoved) {
5406
+ if (adj?.moved) {
5690
5407
  justInteractedRef.current = true;
5691
- }
5692
- if (wasMoved) {
5693
5408
  livePatcher?.flush();
5694
5409
  }
5695
5410
  marqueeStateRef.current = null;
5696
- dragStateRef.current = null;
5697
- resizeStateRef.current = null;
5698
5411
  shapeAdjustmentDragStateRef.current = null;
5699
5412
  setMarqueeSelectionState(null);
5700
- setSnapLines([]);
5701
- if (wasMoved) {
5413
+ if (adj?.moved) {
5702
5414
  markDirty();
5703
5415
  }
5704
- if (drag || rs || adj || marquee) {
5416
+ if (adj || marquee) {
5705
5417
  setPointerCommitNonce((n) => n + 1);
5706
5418
  }
5707
5419
  }
@@ -5721,35 +5433,6 @@ function commitMarquee(marquee, editTemplateMode, templateElements, activeSlide,
5721
5433
  clearSelection();
5722
5434
  }
5723
5435
  }
5724
- function commitDrag(drag, editTemplateMode, activeSlide, activeSlideIndex, setTemplateElementsBySlideId, updateSlides) {
5725
- const dx = drag.lastDx, dy = drag.lastDy;
5726
- const movedIds = new Set(Object.keys(drag.startPositionsById));
5727
- const moveElements = (elements) => {
5728
- const movedElements = elements.map((el) => {
5729
- const start = drag.startPositionsById[el.id];
5730
- if (!start) {
5731
- return el;
5732
- }
5733
- return { ...el, x: start.x + dx, y: start.y + dy };
5734
- });
5735
- const rerouted = rerouteConnectorsForMovedElements(movedElements, movedIds);
5736
- return applyReroutedConnectors(movedElements, rerouted);
5737
- };
5738
- if (editTemplateMode) {
5739
- const slideId = activeSlide?.id;
5740
- if (!slideId) {
5741
- return;
5742
- }
5743
- setTemplateElementsBySlideId((prev) => ({
5744
- ...prev,
5745
- [slideId]: moveElements(prev[slideId] ?? [])
5746
- }));
5747
- return;
5748
- }
5749
- updateSlides(
5750
- (prev) => prev.map((s, i) => i !== activeSlideIndex ? s : { ...s, elements: moveElements(s.elements) })
5751
- );
5752
- }
5753
5436
 
5754
5437
  // src/viewer/hooks/usePointerHandlers.ts
5755
5438
  function usePointerHandlers(input) {
@@ -5759,41 +5442,42 @@ function usePointerHandlers(input) {
5759
5442
  canvasSize,
5760
5443
  activeSlide,
5761
5444
  activeSlideIndex,
5762
- gridSpacingPx,
5763
- dragStateRef,
5764
- resizeStateRef,
5765
5445
  shapeAdjustmentDragStateRef,
5766
5446
  marqueeStateRef,
5767
5447
  editTemplateMode,
5768
- snapToGrid,
5769
- snapToShape,
5770
- guides,
5771
5448
  templateElements,
5772
- elementLookup,
5773
5449
  setMarqueeSelectionState,
5774
- setSnapLines,
5775
- setTemplateElementsBySlideId,
5776
5450
  setPointerCommitNonce,
5777
5451
  applySelection,
5778
5452
  clearSelection,
5779
- updateSlides,
5780
5453
  updateElementById,
5781
5454
  markDirty,
5782
- livePatcher
5455
+ livePatcher,
5456
+ gestureRef
5783
5457
  } = input;
5458
+ const inputRef = useRef(input);
5459
+ inputRef.current = input;
5460
+ const gestures = useMemo(() => createCanvasGestures(() => inputRef.current), []);
5784
5461
  useEffect(() => {
5785
- const tracker = {
5786
- rafId: 0,
5787
- pendingMoveEvent: null,
5788
- lastSnapLinesKey: ""
5462
+ if (gestureRef) {
5463
+ gestureRef.current = gestures;
5464
+ }
5465
+ return () => {
5466
+ gestures.dispose();
5467
+ if (gestureRef?.current === gestures) {
5468
+ gestureRef.current = null;
5469
+ }
5789
5470
  };
5471
+ }, [gestures, gestureRef]);
5472
+ useEffect(() => {
5473
+ const tracker = { rafId: 0, pendingMoveEvent: null };
5790
5474
  const handlePointerMove = (e) => {
5791
5475
  tracker.pendingMoveEvent = e;
5792
5476
  if (tracker.rafId === 0) {
5793
5477
  tracker.rafId = requestAnimationFrame(() => {
5794
5478
  tracker.rafId = 0;
5795
5479
  if (tracker.pendingMoveEvent) {
5796
- processPointerMove(tracker.pendingMoveEvent, input, tracker);
5480
+ processPointerMove(tracker.pendingMoveEvent, input);
5797
5481
  tracker.pendingMoveEvent = null;
5798
5482
  }
5799
5483
  });
@@ -5821,31 +5505,22 @@ function usePointerHandlers(input) {
5821
5505
  }, [
5822
5506
  editorScale,
5823
5507
  canvasStageRef,
5824
- dragStateRef,
5825
- resizeStateRef,
5826
5508
  shapeAdjustmentDragStateRef,
5827
5509
  marqueeStateRef,
5828
5510
  editTemplateMode,
5829
- snapToGrid,
5830
- snapToShape,
5831
- guides,
5832
5511
  templateElements,
5833
5512
  setMarqueeSelectionState,
5834
- setSnapLines,
5835
- elementLookup,
5836
- setTemplateElementsBySlideId,
5837
5513
  setPointerCommitNonce,
5838
5514
  activeSlide,
5839
5515
  activeSlideIndex,
5840
5516
  canvasSize.width,
5841
5517
  canvasSize.height,
5842
- gridSpacingPx,
5843
5518
  applySelection,
5844
5519
  clearSelection,
5845
- updateSlides,
5846
5520
  updateElementById,
5847
5521
  markDirty,
5848
- livePatcher
5522
+ livePatcher,
5523
+ gestureRef
5849
5524
  ]);
5850
5525
  }
5851
5526
  function mouseDownStartsSelectionDrag(event) {
@@ -5871,6 +5546,7 @@ function useCanvasInteractions(input) {
5871
5546
  shapeAdjustmentDragStateRef,
5872
5547
  marqueeStateRef,
5873
5548
  justInteractedRef,
5549
+ gestureRef,
5874
5550
  setInlineEditingElementId,
5875
5551
  setInlineEditingText,
5876
5552
  setContextMenuState,
@@ -5896,25 +5572,7 @@ function useCanvasInteractions(input) {
5896
5572
  const scale = editorScale || 1;
5897
5573
  return rect ? { x: (e.clientX - rect.left) / scale, y: (e.clientY - rect.top) / scale } : null;
5898
5574
  };
5899
- const drillChain = (elementId, e) => {
5900
- if (!slideElements) {
5901
- return null;
5902
- }
5903
- const top = slideElements.find((el) => el.id === elementId);
5904
- const point = slidePoint(e);
5905
- if (!top || !point || !isEnterableGroup(top)) {
5906
- return null;
5907
- }
5908
- return memberChainAtPoint(slideElements, elementId, point);
5909
- };
5910
- const resolvePressTarget = (elementId, e) => {
5911
- const chain = drillChain(elementId, e);
5912
- if (!chain || !slideElements) {
5913
- return elementId;
5914
- }
5915
- const selectedPath = selectedElementId ? findElementPath(slideElements, selectedElementId) : null;
5916
- return drillSelectionForClick(chain, selectedPath) ?? elementId;
5917
- };
5575
+ const resolvePressTarget = (elementId, e) => slideElements ? resolveGroupPressTarget(slideElements, elementId, slidePoint(e), effectiveSelectedIds) : elementId;
5918
5576
  const handleInlineEditCommit = () => {
5919
5577
  const editId = inlineEditingElementId;
5920
5578
  if (!editId) {
@@ -6014,9 +5672,7 @@ function useCanvasInteractions(input) {
6014
5672
  }
6015
5673
  };
6016
5674
  const handleElementDoubleClick = (pressedId, e) => {
6017
- const chain = mode === "present" ? null : drillChain(pressedId, e);
6018
- const innermost = chain ? drillSelectionForDoubleClick(chain) : null;
6019
- const elementId = innermost && canInteractWithElement(elementLookup.get(innermost), "select") ? innermost : pressedId;
5675
+ const elementId = mode === "present" || !slideElements ? pressedId : resolveGroupDoubleClickTarget(slideElements, pressedId, slidePoint(e));
6020
5676
  if (elementId !== pressedId) {
6021
5677
  ops.applySelection(elementId);
6022
5678
  }
@@ -6079,6 +5735,7 @@ function useCanvasInteractions(input) {
6079
5735
  lastDy: 0
6080
5736
  };
6081
5737
  setSnapLines([]);
5738
+ gestureRef?.current?.begin("move", movableIds[0], e);
6082
5739
  };
6083
5740
  const handleElementContextMenu = (elementId, e) => {
6084
5741
  if (mode === "present") {
@@ -6151,6 +5808,7 @@ function useCanvasInteractions(input) {
6151
5808
  lastWidth: el.width,
6152
5809
  lastHeight: el.height
6153
5810
  };
5811
+ gestureRef?.current?.begin("resize", elementId, e);
6154
5812
  };
6155
5813
  const handleRotate = (elementId, rotationDeg) => {
6156
5814
  const el = elementLookup.get(elementId);
@@ -6656,11 +6314,11 @@ function useInsertElements(input) {
6656
6314
  id: generateElementId()
6657
6315
  });
6658
6316
  };
6659
- const handleAddChart = (chartKind) => {
6317
+ const handleAddChart = (chartKind, options) => {
6660
6318
  if (!activeSlide) {
6661
6319
  return;
6662
6320
  }
6663
- addElement(createDefaultChartElement(chartKind));
6321
+ addElement(createDefaultChartElement(chartKind, void 0, options));
6664
6322
  };
6665
6323
  const structured = createStructuredElementHandlers({
6666
6324
  activeSlide,
@@ -7551,6 +7209,8 @@ async function renderToCanvas(element, options = {}) {
7551
7209
  const { default: html2canvasPro } = await import('html2canvas-pro');
7552
7210
  return html2canvasPro(element, {
7553
7211
  ...options,
7212
+ // Clone only the captured element's branch: html2canvas would copy the whole page.
7213
+ ignoreElements: html2canvasCaptureScope(element, options.ignoreElements),
7554
7214
  onclone: async (doc, clonedEl) => {
7555
7215
  await prepareHtml2CanvasClone(element, doc, clonedEl);
7556
7216
  if (typeof userOnClone === "function") {
@@ -9103,6 +8763,7 @@ function useViewerDialogs(input) {
9103
8763
  embeddedFontFamilies
9104
8764
  } = input;
9105
8765
  const [isSmartArtDialogOpen, setIsSmartArtDialogOpen] = useState(false);
8766
+ const [isChartDialogOpen, setIsChartDialogOpen] = useState(false);
9106
8767
  const [isEquationDialogOpen, setIsEquationDialogOpen] = useState(false);
9107
8768
  const [isHyperlinkDialogOpen, setIsHyperlinkDialogOpen] = useState(false);
9108
8769
  const [isPasswordDialogOpen, setIsPasswordDialogOpen] = useState(false);
@@ -9247,6 +8908,8 @@ function useViewerDialogs(input) {
9247
8908
  return {
9248
8909
  isSmartArtDialogOpen,
9249
8910
  setIsSmartArtDialogOpen,
8911
+ isChartDialogOpen,
8912
+ setIsChartDialogOpen,
9250
8913
  isEquationDialogOpen,
9251
8914
  setIsEquationDialogOpen,
9252
8915
  isHyperlinkDialogOpen,
@@ -9692,84 +9355,35 @@ function useSerialize(input) {
9692
9355
  }
9693
9356
  function useRecoveryDetection(input) {
9694
9357
  const { filePath, fileName, loading, error, slideCount, autosaveAllowed = true } = input;
9695
- const recoveryCheckedRef = useRef(false);
9696
- const recordRef = useRef(null);
9697
- const actionPendingRef = useRef(false);
9698
- const [prompt, setPrompt] = useState(null);
9699
- const [discarding, setDiscarding] = useState(false);
9700
- const onRestore = input.onRestore;
9701
- const storageRef = useRef(input.storage);
9702
- storageRef.current = input.storage;
9703
- const onOfferedRef = useRef(input.onOffered);
9704
- useEffect(() => {
9705
- onOfferedRef.current = input.onOffered;
9706
- }, [input.onOffered]);
9707
- const openVersionHistory = input.openVersionHistory;
9358
+ const live = useRef(input);
9359
+ live.current = input;
9360
+ const [controller] = useState(
9361
+ () => createAutosaveRecoveryController({
9362
+ getStorage: () => live.current.storage,
9363
+ load: (bytes) => live.current.onRestore?.(bytes),
9364
+ onOffered: (record) => {
9365
+ live.current.onOffered?.(record);
9366
+ live.current.openVersionHistory?.();
9367
+ },
9368
+ probe: probeAutosaveRecovery,
9369
+ discardRecord: discardAutosaveRecovery
9370
+ })
9371
+ );
9372
+ const { prompt, discarding } = useController(controller);
9708
9373
  useEffect(() => {
9709
- if (!shouldProbeAutosaveRecovery({
9710
- alreadyChecked: recoveryCheckedRef.current,
9711
- filePath,
9712
- loading,
9713
- error,
9714
- slideCount,
9715
- autosaveAllowed
9716
- })) {
9717
- return;
9718
- }
9719
- recoveryCheckedRef.current = true;
9720
- void (async () => {
9721
- const offer = await probeAutosaveRecovery(
9722
- filePath,
9723
- Date.now(),
9724
- fileName,
9725
- storageRef.current
9726
- );
9727
- if (!offer) {
9728
- return;
9729
- }
9730
- recordRef.current = offer.record;
9731
- setPrompt(offer.prompt);
9732
- onOfferedRef.current?.(offer.record);
9733
- openVersionHistory?.();
9734
- })();
9735
- }, [filePath, fileName, loading, error, slideCount, autosaveAllowed, openVersionHistory]);
9736
- const restore = useCallback(() => {
9737
- if (actionPendingRef.current) {
9738
- return;
9739
- }
9740
- actionPendingRef.current = true;
9741
- const record = recordRef.current;
9742
- setPrompt(null);
9743
- if (record && onRestore) {
9744
- onRestore(acceptAutosaveRecovery(record));
9745
- acknowledgeAutosaveRecovery(record);
9746
- }
9747
- recordRef.current = null;
9748
- }, [onRestore]);
9749
- const discard = useCallback(() => {
9750
- if (actionPendingRef.current) {
9751
- return;
9752
- }
9753
- const record = recordRef.current;
9754
- if (!record) {
9755
- setPrompt(null);
9756
- return;
9374
+ controller.check({ filePath, fileName, loading, error, slideCount, autosaveAllowed });
9375
+ }, [controller, filePath, fileName, loading, error, slideCount, autosaveAllowed]);
9376
+ return {
9377
+ prompt,
9378
+ discarding,
9379
+ restore: () => {
9380
+ void controller.restore().catch(() => {
9381
+ });
9382
+ },
9383
+ discard: () => {
9384
+ void controller.discard();
9757
9385
  }
9758
- actionPendingRef.current = true;
9759
- setDiscarding(true);
9760
- void (async () => {
9761
- try {
9762
- await discardAutosaveRecovery(record, storageRef.current);
9763
- recordRef.current = null;
9764
- setPrompt(null);
9765
- } catch {
9766
- } finally {
9767
- actionPendingRef.current = false;
9768
- setDiscarding(false);
9769
- }
9770
- })();
9771
- }, []);
9772
- return { prompt, restore, discard, discarding };
9386
+ };
9773
9387
  }
9774
9388
  function usePresentationSetup(input) {
9775
9389
  const {
@@ -10243,6 +9857,7 @@ function useEditorOperations(input) {
10243
9857
  shapeAdjustmentDragStateRef: state.shapeAdjustmentDragStateRef,
10244
9858
  marqueeStateRef: state.marqueeStateRef,
10245
9859
  justInteractedRef: state.justInteractedRef,
9860
+ gestureRef: state.gestureControllerRef,
10246
9861
  setInlineEditingElementId: state.setInlineEditingElementId,
10247
9862
  setInlineEditingText: state.setInlineEditingText,
10248
9863
  setContextMenuState: state.setContextMenuState,
@@ -10775,6 +10390,8 @@ function useContentLifecycle(input) {
10775
10390
  // deck it has already snapshotted. The two refs are read by `.current`
10776
10391
  // because an inline edit in progress lives there and nowhere else yet.
10777
10392
  onSaved: input.onAutosave,
10393
+ inlineDraftSinkRef: state.autosaveDraftSinkRef,
10394
+ getInlineEditingId: () => state.inlineEditingElementIdRef.current,
10778
10395
  getChangeSources: () => [
10779
10396
  slides,
10780
10397
  state.templateElementsBySlideId,
@@ -10804,138 +10421,27 @@ function useKeyboardShortcuts(input) {
10804
10421
  inputRef.current = input;
10805
10422
  const handleKeyDown = useCallback((e) => {
10806
10423
  const current = inputRef.current;
10807
- const startAction = current.customization && !isFeatureEnabled(current.customization, "presentMode") ? null : mapSlideShowStartKey(e, { isPresenting: current.mode === "present" });
10808
- if (startAction !== null) {
10809
- e.preventDefault();
10810
- if (startAction === "fromBeginning") {
10811
- current.onStartShowFromBeginning();
10812
- } else {
10813
- current.onStartShowFromCurrent();
10814
- }
10815
- return;
10816
- }
10817
- const { action, dx, dy } = mapCustomizedEditorKey(
10424
+ dispatchEditorKey(
10818
10425
  e,
10819
10426
  {
10820
10427
  canEdit: current.canEdit,
10821
- canPaste: current.canPaste,
10822
- // The Slide Master view is an editing surface, not a viewing one.
10823
- // Gating on `mode !== 'edit'` alone made `mapEditorKey` return
10824
- // NO_ACTION there, so Delete, the arrow-key nudges and the clipboard
10825
- // keys were all inert over a master shape even though the write path
10826
- // behind them routes to the owning part correctly. Svelte and
10827
- // vanilla, which render the master into their ordinary editable
10828
- // stage, never had the gap.
10428
+ ...current.canPaste === void 0 ? {} : { canPaste: current.canPaste },
10429
+ // The Slide Master view is an editing surface, not a viewing one, so
10430
+ // only the other modes count as "not editing" for the editor keymap.
10829
10431
  isPresenting: current.mode !== "edit" && current.mode !== "master",
10432
+ // F5 is only inert while the show itself runs: a read-only viewer can
10433
+ // still start a show (it has a "From Beginning" button too).
10434
+ isShowRunning: current.mode === "present",
10435
+ canStartShow: !current.customization || isFeatureEnabled(current.customization, "presentMode"),
10830
10436
  hasSelection: current.hasSelection,
10831
10437
  isEditingText: Boolean(
10832
10438
  current.inlineEditingElementId || current.tableEditorState?.isEditing
10833
10439
  ),
10834
10440
  isDrawing: current.activeTool !== "select",
10835
- isTextInputTarget: isEditorTextInputTarget(e.target),
10836
- isControlTarget: isEditorControlTarget(e.target)
10441
+ keyboard: current.customization?.keyboard
10837
10442
  },
10838
- current.customization?.keyboard
10443
+ toEditorKeyHandlers(current)
10839
10444
  );
10840
- if (action === null) {
10841
- return;
10842
- }
10843
- e.preventDefault();
10844
- switch (action) {
10845
- case "escape":
10846
- current.onEscape();
10847
- break;
10848
- case "delete":
10849
- current.onDelete();
10850
- break;
10851
- case "undo":
10852
- current.onUndo();
10853
- break;
10854
- case "redo":
10855
- current.onRedo();
10856
- break;
10857
- case "copy":
10858
- current.onCopy();
10859
- break;
10860
- case "cut":
10861
- current.onCut();
10862
- break;
10863
- case "paste":
10864
- current.onPaste();
10865
- break;
10866
- case "duplicate":
10867
- current.onDuplicate();
10868
- break;
10869
- case "selectAll":
10870
- current.onSelectAll();
10871
- break;
10872
- case "group":
10873
- current.onGroup?.();
10874
- break;
10875
- case "ungroup":
10876
- current.onUngroup?.();
10877
- break;
10878
- case "toggleShortcuts":
10879
- current.onToggleShortcuts?.();
10880
- break;
10881
- case "find":
10882
- current.onFind?.();
10883
- break;
10884
- case "nudge":
10885
- current.onNudge(dx ?? 0, dy ?? 0);
10886
- break;
10887
- case "prevSlide":
10888
- current.onPrevSlide?.();
10889
- break;
10890
- case "nextSlide":
10891
- current.onNextSlide?.();
10892
- break;
10893
- case "alignLeft":
10894
- current.onAlignLeft?.();
10895
- break;
10896
- case "alignCenter":
10897
- current.onAlignCenter?.();
10898
- break;
10899
- case "alignRight":
10900
- current.onAlignRight?.();
10901
- break;
10902
- case "alignJustify":
10903
- current.onAlignJustify?.();
10904
- break;
10905
- case "increaseFontSize":
10906
- current.onIncreaseFontSize?.();
10907
- break;
10908
- case "decreaseFontSize":
10909
- current.onDecreaseFontSize?.();
10910
- break;
10911
- case "copyFormat":
10912
- current.onCopyFormat?.();
10913
- break;
10914
- case "pasteFormat":
10915
- current.onPasteFormat?.();
10916
- break;
10917
- case "newSlide":
10918
- current.onNewSlide?.();
10919
- break;
10920
- case "hyperlink":
10921
- current.onHyperlink?.();
10922
- break;
10923
- case "findReplace":
10924
- current.onFindReplace?.();
10925
- break;
10926
- case "clearFormatting":
10927
- current.onClearFormatting?.();
10928
- break;
10929
- case "cycleSelectionNext":
10930
- current.onCycleSelectionNext?.();
10931
- break;
10932
- case "cycleSelectionPrev":
10933
- current.onCycleSelectionPrev?.();
10934
- break;
10935
- case "pasteSpecial":
10936
- current.onPasteSpecial?.();
10937
- break;
10938
- }
10939
10445
  }, []);
10940
10446
  useEffect(() => {
10941
10447
  window.addEventListener("keydown", handleKeyDown);
@@ -10944,6 +10450,43 @@ function useKeyboardShortcuts(input) {
10944
10450
  };
10945
10451
  }, [handleKeyDown]);
10946
10452
  }
10453
+ function toEditorKeyHandlers(current) {
10454
+ return {
10455
+ startShowFromBeginning: current.onStartShowFromBeginning,
10456
+ startShowFromCurrent: current.onStartShowFromCurrent,
10457
+ escape: current.onEscape,
10458
+ delete: current.onDelete,
10459
+ undo: current.onUndo,
10460
+ redo: current.onRedo,
10461
+ copy: current.onCopy,
10462
+ cut: current.onCut,
10463
+ paste: current.onPaste,
10464
+ duplicate: current.onDuplicate,
10465
+ selectAll: current.onSelectAll,
10466
+ group: current.onGroup,
10467
+ ungroup: current.onUngroup,
10468
+ toggleShortcuts: current.onToggleShortcuts,
10469
+ find: current.onFind,
10470
+ nudge: current.onNudge,
10471
+ prevSlide: current.onPrevSlide,
10472
+ nextSlide: current.onNextSlide,
10473
+ alignLeft: current.onAlignLeft,
10474
+ alignCenter: current.onAlignCenter,
10475
+ alignRight: current.onAlignRight,
10476
+ alignJustify: current.onAlignJustify,
10477
+ increaseFontSize: current.onIncreaseFontSize,
10478
+ decreaseFontSize: current.onDecreaseFontSize,
10479
+ copyFormat: current.onCopyFormat,
10480
+ pasteFormat: current.onPasteFormat,
10481
+ newSlide: current.onNewSlide,
10482
+ hyperlink: current.onHyperlink,
10483
+ findReplace: current.onFindReplace,
10484
+ clearFormatting: current.onClearFormatting,
10485
+ cycleSelectionNext: current.onCycleSelectionNext,
10486
+ cycleSelectionPrev: current.onCycleSelectionPrev,
10487
+ pasteSpecial: current.onPasteSpecial
10488
+ };
10489
+ }
10947
10490
  var CLEAR_FORMATTING_PATCH = {
10948
10491
  bold: false,
10949
10492
  italic: false,
@@ -11209,7 +10752,8 @@ function useViewerIntegration(input) {
11209
10752
  updateSlides: editorOps.ops.updateSlides,
11210
10753
  updateElementById: editorOps.ops.updateElementById,
11211
10754
  markDirty: history.markDirty,
11212
- livePatcher: state.livePatcher
10755
+ livePatcher: state.livePatcher,
10756
+ gestureRef: state.gestureControllerRef
11213
10757
  });
11214
10758
  const [isEncryptedDialogOpen, setIsEncryptedDialogOpen] = useState(false);
11215
10759
  const [loadVersion, setLoadVersion] = useState(0);
@@ -12646,6 +12190,7 @@ function buildToolbarProps(input) {
12646
12190
  onAddShape: insertHandlers.handleAddShape,
12647
12191
  onAddTable: insertHandlers.handleAddTable,
12648
12192
  onAddChart: insertHandlers.handleAddChart,
12193
+ onOpenChartDialog: () => dialogs.setIsChartDialogOpen(true),
12649
12194
  onAddSmartArt: () => dialogs.setIsSmartArtDialogOpen(true),
12650
12195
  onAddEquation: () => {
12651
12196
  dialogs.setEditingEquationOmml(null);
@@ -13178,73 +12723,47 @@ function useReadOnlyRecommendationState(content) {
13178
12723
  () => readOnlyRecommendation(void 0)
13179
12724
  );
13180
12725
  const [modifyVerifier, setModifyVerifier] = useState(void 0);
13181
- const [bannerHidden, setBannerHidden] = useState(false);
13182
- const [editAnywayActive, setEditAnywayActive] = useState(false);
13183
- const [passwordPromptOpen, setPasswordPromptOpen] = useState(false);
13184
- const [passwordError, setPasswordError] = useState(null);
13185
- const [checkingPassword, setCheckingPassword] = useState(false);
13186
- useEffect(() => {
13187
- setBannerHidden(false);
13188
- setEditAnywayActive(false);
13189
- setPasswordPromptOpen(false);
13190
- setPasswordError(null);
13191
- setCheckingPassword(false);
13192
- }, [content]);
13193
- const unlock = useCallback(() => {
13194
- setEditAnywayActive(true);
13195
- setBannerHidden(true);
13196
- setPasswordPromptOpen(false);
13197
- setPasswordError(null);
13198
- }, []);
13199
- const editAnyway = useCallback(() => {
13200
- if (recommendation.requiresPassword) {
13201
- setPasswordPromptOpen(true);
13202
- setPasswordError(null);
13203
- return;
13204
- }
13205
- unlock();
13206
- }, [recommendation.requiresPassword, unlock]);
13207
- const dismiss = useCallback(() => {
13208
- setBannerHidden(true);
13209
- }, []);
13210
- const cancelPasswordPrompt = useCallback(() => {
13211
- setPasswordPromptOpen(false);
13212
- setPasswordError(null);
13213
- }, []);
13214
- const submitPassword = useCallback(
13215
- async (password) => {
13216
- setCheckingPassword(true);
13217
- try {
13218
- const result = await checkModifyPassword(modifyVerifier, password);
13219
- if (result.ok) {
13220
- unlock();
13221
- } else {
13222
- setPasswordError(result.reason);
13223
- }
13224
- } finally {
13225
- setCheckingPassword(false);
12726
+ const [session, setSession] = useState(INITIAL_READ_ONLY_SESSION);
12727
+ const latestSession = useRef(session);
12728
+ const store = useMemo(
12729
+ () => ({
12730
+ get: () => latestSession.current,
12731
+ set: (next) => {
12732
+ latestSession.current = next;
12733
+ setSession(next);
13226
12734
  }
13227
- },
13228
- // oxlint-disable-next-line react/memo-dependencies -- see comment above
13229
- [modifyVerifier, unlock]
12735
+ }),
12736
+ []
13230
12737
  );
12738
+ const actions = useMemo(
12739
+ () => readOnlySessionActions(store, {
12740
+ recommendation: () => recommendation,
12741
+ modifyVerifier: () => modifyVerifier
12742
+ }),
12743
+ [store, recommendation, modifyVerifier]
12744
+ );
12745
+ useEffect(() => {
12746
+ applyReadOnlySessionEvent(store, { type: "reset" });
12747
+ }, [content, store]);
13231
12748
  return {
13232
12749
  recommendation,
13233
12750
  setRecommendation,
13234
12751
  setModifyVerifier,
13235
12752
  /** Whether the viewer's `canEdit` should be forced off. */
13236
- locked: recommendation.defaultReadOnly && !editAnywayActive,
13237
- bannerVisible: recommendation.kind !== null && !bannerHidden,
13238
- editAnyway,
13239
- dismiss,
12753
+ locked: readOnlyLocked(recommendation, session),
12754
+ bannerVisible: readOnlyBannerVisible(recommendation, session),
12755
+ /** The banner's "Edit anyway": opens the password prompt when one is required, else unlocks. */
12756
+ editAnyway: actions.editAnyway,
12757
+ dismiss: actions.dismiss,
13240
12758
  /** Whether the inline password prompt should render in place of the banner's buttons. */
13241
- passwordPromptOpen,
12759
+ passwordPromptOpen: session.passwordPromptOpen,
13242
12760
  /** Reason the last password attempt failed, or null before any attempt / after success. */
13243
- passwordError,
12761
+ passwordError: session.passwordError,
13244
12762
  /** True while `submitPassword`'s check is in flight (disables the form). */
13245
- checkingPassword,
13246
- submitPassword,
13247
- cancelPasswordPrompt
12763
+ checkingPassword: session.checkingPassword,
12764
+ /** Check `password` against the deck's `modifyVerifier`; unlocks on a match. */
12765
+ submitPassword: actions.submitPassword,
12766
+ cancelPasswordPrompt: actions.cancelPassword
13248
12767
  };
13249
12768
  }
13250
12769
  function useViewPreferencesSync(input) {
@@ -14531,6 +14050,50 @@ var EDGE_HANDLES = [
14531
14050
  sizeClass: "w-2 h-5 max-md:w-3 max-md:h-8 rounded-sm"
14532
14051
  }
14533
14052
  ];
14053
+ var wholeDegrees = (deg) => Math.round((deg % 360 + 360) % 360) % 360;
14054
+ function startRotateKnobGesture(options) {
14055
+ const { button, elementId, rotation, onRotate } = options;
14056
+ const scope = button.closest("[data-pptx-viewport]") ?? document;
14057
+ const wrapper = scope.querySelector(elementIdSelector(elementId));
14058
+ if (!wrapper) {
14059
+ return;
14060
+ }
14061
+ const rect = wrapper.getBoundingClientRect();
14062
+ const base = options.nonRotationTransform ? ` ${options.nonRotationTransform}` : "";
14063
+ const paint = (deg) => {
14064
+ const transform = `rotate(${deg}deg)${base}`;
14065
+ wrapper.style.transform = transform;
14066
+ syncSelectionHandleOverlay(wrapper, elementId, { transform });
14067
+ };
14068
+ if (options.pointerId !== void 0) {
14069
+ button.setPointerCapture?.(options.pointerId);
14070
+ }
14071
+ const gesture = createGestureController({
14072
+ getScale: () => 1,
14073
+ getElementBox: () => ({
14074
+ x: rect.left,
14075
+ y: rect.top,
14076
+ width: rect.width,
14077
+ height: rect.height,
14078
+ rotation
14079
+ }),
14080
+ getSiblings: () => [],
14081
+ getStageOrigin: () => ({ left: 0, top: 0 }),
14082
+ onStart: () => void 0,
14083
+ onPreview: (transform) => paint(wholeDegrees(transform.rotation)),
14084
+ onEnd: (transform) => {
14085
+ const deg = transform ? wholeDegrees(transform.rotation) : rotation;
14086
+ if (deg !== rotation) {
14087
+ onRotate?.(elementId, deg);
14088
+ }
14089
+ }
14090
+ });
14091
+ gesture.begin("rotate", elementId, {
14092
+ clientX: options.pointer.clientX,
14093
+ clientY: options.pointer.clientY,
14094
+ shiftKey: false
14095
+ });
14096
+ }
14534
14097
  var appearance = { fill: "var(--color-primary)", borderColor: "#ffffff" };
14535
14098
  var cornerSize = "var(--pptx-react-corner-default, 12px)";
14536
14099
  var edgeLength = "var(--pptx-react-edge-length-default, 20px)";
@@ -14644,50 +14207,15 @@ function ResizeHandles({
14644
14207
  e.currentTarget.setPointerCapture?.(e.pointerId);
14645
14208
  onResizePointerDown(elementId, e, handle);
14646
14209
  };
14647
- const startRotate = (btn, pointer, pointerId) => {
14648
- const scope = btn.closest("[data-pptx-viewport]") ?? document;
14649
- const wrapper = scope.querySelector(elementIdSelector(elementId));
14650
- if (!wrapper) {
14651
- return;
14652
- }
14653
- const rect = wrapper.getBoundingClientRect();
14654
- const cx = rect.left + rect.width / 2;
14655
- const cy = rect.top + rect.height / 2;
14656
- const base = nonRotationTransform ? ` ${nonRotationTransform}` : "";
14657
- const startDeg = rotation ?? 0;
14658
- const dragRotation = createRotationDrag(
14659
- { x: cx, y: cy },
14660
- { x: pointer.clientX, y: pointer.clientY },
14661
- startDeg
14662
- );
14663
- let last = startDeg;
14664
- if (pointerId !== void 0) {
14665
- btn.setPointerCapture?.(pointerId);
14666
- }
14667
- const apply = (clientX, clientY, shift) => {
14668
- let deg = dragRotation({ x: clientX, y: clientY });
14669
- if (shift) {
14670
- deg = Math.round(deg / 15) * 15;
14671
- }
14672
- deg = Math.round((deg % 360 + 360) % 360);
14673
- last = deg;
14674
- const transform = `rotate(${deg}deg)${base}`;
14675
- wrapper.style.transform = transform;
14676
- syncSelectionHandleOverlay(wrapper, elementId, { transform });
14677
- };
14678
- const onPointerMove = (ev) => apply(ev.clientX, ev.clientY, ev.shiftKey);
14679
- const end = () => {
14680
- window.removeEventListener("pointermove", onPointerMove);
14681
- window.removeEventListener("pointerup", end);
14682
- window.removeEventListener("pointercancel", end);
14683
- if (last !== startDeg) {
14684
- onRotate?.(elementId, last);
14685
- }
14686
- };
14687
- window.addEventListener("pointermove", onPointerMove);
14688
- window.addEventListener("pointerup", end);
14689
- window.addEventListener("pointercancel", end);
14690
- };
14210
+ const startRotate = (button, pointer) => startRotateKnobGesture({
14211
+ button,
14212
+ elementId,
14213
+ pointer,
14214
+ pointerId: pointer.pointerId,
14215
+ rotation: rotation ?? 0,
14216
+ ...nonRotationTransform ? { nonRotationTransform } : {},
14217
+ ...onRotate ? { onRotate } : {}
14218
+ });
14691
14219
  return /* @__PURE__ */ jsxs(Fragment, { children: [
14692
14220
  [...CORNER_HANDLES, ...EDGE_HANDLES].map(({ handle, posClass, cursor }) => {
14693
14221
  const corner = handle.length === 2;
@@ -14758,7 +14286,7 @@ function ResizeHandles({
14758
14286
  },
14759
14287
  onPointerDown: (e) => {
14760
14288
  e.stopPropagation();
14761
- startRotate(e.currentTarget, e, e.pointerId);
14289
+ startRotate(e.currentTarget, e);
14762
14290
  },
14763
14291
  onMouseDown: (e) => {
14764
14292
  e.stopPropagation();
@@ -19168,4 +18696,4 @@ function useResizablePanels() {
19168
18696
  };
19169
18697
  }
19170
18698
 
19171
- export { ACTION_VERB_MAP, ChartFormatContext, ChartPartSelectionProvider, CollaborationCursorOverlay, CollaborationProvider, ElementRenderer, FragmentedTransitionLayer, InlineCollaborationContext, MIN_TOUCH_TARGET, MorphTransitionOverlay, PresentationTransitionOverlay, RemoteSelectionOverlay, RemoteUserCursors, Rendering3DFlagsContext, ResizeHandles, SlideBackgroundImageLayer, SlideLayer, SmartArtRenderer, StaticElementRenderer, ViewerOptionsContext, ViewerOptionsStoreContext, computeToolbarVisibility, createReviewCommentsHost, detectTargetType, imgSrc, isAudienceTab, parseAudienceNonce, parseEmailUrl, parseSlideFromUrl, playAnimationSound, renderStrokeView, renderToCanvas, resolveSmartArtDataPalette, safePrompt, scopeLayoutOptionsToActiveSlide, useAnimationPlayback, useAnnotationHandlers, useAudienceMode, useAutosave, useBroadcastFollower, useCanvasInteractions, useChartFormatContext, useChartPartSelection, useClipboardHandlers, useCollaboration, useCollaborationDocumentSync, useCollaborativeHistory, useCollaborativeState, useComments, useCompatibilityToastsState, useContentLifecycle, useDerivedElementState, useDerivedSlideState, useDialogCustomShows, useEditorHistory, useEditorOperations, useElementManipulation, useElementOperations, useExportHandlers, useExportSaveAs, useFindReplace, useFollowMode, useFontInjection, useGroupAlignLayerHandlers, useIOHandlers, useInsertElements, useIsMobile, useKeyboardInsets, useKeyboardShortcutWiring, useKeyboardShortcuts, useLayoutSwitching, useLoadContent, useMergeShapesHandler, useModalDismissDrag, usePointerHandlers, usePresenceTracking, usePresentationAnnotations, usePresentationKeyboard, usePresentationMode, usePresentationSetup, usePresenterWindow, usePrintHandlers, usePropertyHandlers, useReadOnlyRecommendationState, useRecoveryDetection, useReducedMotion, useRehearsalTimings, useReportOnChange, useResizablePanels, useSectionOperations, useSerialize, useSheetDismissDrag, useSlideManagement, useSlideNavigation, useSwipeNavigation, useTableOperations, useThemeHandlers, useThemeSwitching, useToolbarVisibility, useTouchGestures, useViewPreferencesSync, useViewerBuildingBlocks, useViewerCoreState, useViewerDialogs, useViewerIntegration, useViewerOptions, useViewerOptionsContext, useViewerState, useViewerUIState, useVirtualizedSlides, useYjsDocumentSync, useYjsProvider, useZoomNavigation, useZoomViewport, withDisplayName };
18699
+ export { ACTION_VERB_MAP, ChartFormatContext, ChartPartSelectionProvider, CollaborationCursorOverlay, CollaborationProvider, ElementRenderer, FragmentedTransitionLayer, InlineCollaborationContext, MIN_TOUCH_TARGET, MorphTransitionOverlay, PresentationTransitionOverlay, RemoteSelectionOverlay, RemoteUserCursors, Rendering3DFlagsContext, ResizeHandles, SlideBackgroundImageLayer, SlideLayer, StaticElementRenderer, ViewerOptionsContext, ViewerOptionsStoreContext, computeToolbarVisibility, createReviewCommentsHost, detectTargetType, imgSrc, isAudienceTab, parseAudienceNonce, parseEmailUrl, parseSlideFromUrl, playAnimationSound, renderStrokeView, renderToCanvas, resolveSmartArtDataPalette, safePrompt, scopeLayoutOptionsToActiveSlide, useAnimationPlayback, useAnnotationHandlers, useAudienceMode, useAutosave, useBroadcastFollower, useCanvasInteractions, useChartFormatContext, useChartPartSelection, useClipboardHandlers, useCollaboration, useCollaborationDocumentSync, useCollaborativeHistory, useCollaborativeState, useComments, useCompatibilityToastsState, useContentLifecycle, useController, useDerivedElementState, useDerivedSlideState, useDialogCustomShows, useEditorHistory, useEditorOperations, useElementManipulation, useElementOperations, useExportHandlers, useExportSaveAs, useFindReplace, useFollowMode, useFontInjection, useGroupAlignLayerHandlers, useIOHandlers, useInsertElements, useIsMobile, useKeyboardInsets, useKeyboardShortcutWiring, useKeyboardShortcuts, useLayoutSwitching, useLoadContent, useMergeShapesHandler, useModalDismissDrag, usePointerHandlers, usePresenceTracking, usePresentationAnnotations, usePresentationKeyboard, usePresentationMode, usePresentationSetup, usePresenterWindow, usePrintHandlers, usePropertyHandlers, useReadOnlyRecommendationState, useRecoveryDetection, useReducedMotion, useRehearsalTimings, useReportOnChange, useResizablePanels, useSectionOperations, useSerialize, useSheetDismissDrag, useSlideManagement, useSlideNavigation, useSwipeNavigation, useTableOperations, useThemeHandlers, useThemeSwitching, useToolbarVisibility, useTouchGestures, useViewPreferencesSync, useViewerBuildingBlocks, useViewerCoreState, useViewerDialogs, useViewerIntegration, useViewerOptions, useViewerOptionsContext, useViewerState, useViewerUIState, useVirtualizedSlides, useYjsDocumentSync, useYjsProvider, useZoomNavigation, useZoomViewport, withDisplayName };