pptx-react-viewer 4.49.0 → 4.51.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 (124) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/README.md +38 -4
  3. package/dist/{AiChatPanel-S4WNQCY4.js → AiChatPanel-EV22O7UO.js} +19 -20
  4. package/dist/AiChatPanel-EV22O7UO.js.br +0 -0
  5. package/dist/AiChatPanel-EV22O7UO.js.gz +0 -0
  6. package/dist/{AiChatPanel-NKTBHT5B.mjs → AiChatPanel-R323KNGN.mjs} +1 -2
  7. package/dist/AiChatPanel-R323KNGN.mjs.br +0 -0
  8. package/dist/AiChatPanel-R323KNGN.mjs.gz +0 -0
  9. package/dist/{chunk-Z62JFXMZ.mjs → chunk-ACFR4LFQ.mjs} +577 -1028
  10. package/dist/chunk-ACFR4LFQ.mjs.br +0 -0
  11. package/dist/chunk-ACFR4LFQ.mjs.gz +0 -0
  12. package/dist/chunk-DDJ634MV.js.br +0 -0
  13. package/dist/chunk-DDJ634MV.js.gz +0 -0
  14. package/dist/{chunk-YKLL2Z33.mjs → chunk-ETJ752B5.mjs} +597 -1871
  15. package/dist/chunk-ETJ752B5.mjs.br +0 -0
  16. package/dist/chunk-ETJ752B5.mjs.gz +0 -0
  17. package/dist/chunk-QIUK53JR.mjs.br +0 -0
  18. package/dist/chunk-QIUK53JR.mjs.gz +0 -0
  19. package/dist/{chunk-ME5QRTVS.js → chunk-V3XADJ2O.js} +763 -1214
  20. package/dist/chunk-V3XADJ2O.js.br +0 -0
  21. package/dist/chunk-V3XADJ2O.js.gz +0 -0
  22. package/dist/{chunk-2ZXXHNNR.js → chunk-VI5DQRQZ.js} +2052 -3325
  23. package/dist/chunk-VI5DQRQZ.js.br +0 -0
  24. package/dist/chunk-VI5DQRQZ.js.gz +0 -0
  25. package/dist/{chunk-JAI42EXQ.js → chunk-Z22RD3E2.js} +41 -12
  26. package/dist/chunk-Z22RD3E2.js.br +0 -0
  27. package/dist/chunk-Z22RD3E2.js.gz +0 -0
  28. package/dist/{chunk-PFV626ED.mjs → chunk-ZCGYPCZM.mjs} +37 -8
  29. package/dist/chunk-ZCGYPCZM.mjs.br +0 -0
  30. package/dist/chunk-ZCGYPCZM.mjs.gz +0 -0
  31. package/dist/i18n/de.cjs +1138 -221
  32. package/dist/i18n/de.cjs.br +0 -0
  33. package/dist/i18n/de.cjs.gz +0 -0
  34. package/dist/i18n/de.mjs +1138 -221
  35. package/dist/i18n/de.mjs.br +0 -0
  36. package/dist/i18n/de.mjs.gz +0 -0
  37. package/dist/i18n/es.cjs +1138 -221
  38. package/dist/i18n/es.cjs.br +0 -0
  39. package/dist/i18n/es.cjs.gz +0 -0
  40. package/dist/i18n/es.mjs +1138 -221
  41. package/dist/i18n/es.mjs.br +0 -0
  42. package/dist/i18n/es.mjs.gz +0 -0
  43. package/dist/i18n/fr.cjs +1138 -221
  44. package/dist/i18n/fr.cjs.br +0 -0
  45. package/dist/i18n/fr.cjs.gz +0 -0
  46. package/dist/i18n/fr.mjs +1138 -221
  47. package/dist/i18n/fr.mjs.br +0 -0
  48. package/dist/i18n/fr.mjs.gz +0 -0
  49. package/dist/i18n/zh-CN.cjs +1138 -221
  50. package/dist/i18n/zh-CN.cjs.br +0 -0
  51. package/dist/i18n/zh-CN.cjs.gz +0 -0
  52. package/dist/i18n/zh-CN.mjs +1138 -221
  53. package/dist/i18n/zh-CN.mjs.br +0 -0
  54. package/dist/i18n/zh-CN.mjs.gz +0 -0
  55. package/dist/index.d.ts +56 -27
  56. package/dist/index.js +28 -24
  57. package/dist/index.js.br +0 -0
  58. package/dist/index.js.gz +0 -0
  59. package/dist/index.mjs +3 -3
  60. package/dist/index.mjs.br +0 -0
  61. package/dist/index.mjs.gz +0 -0
  62. package/dist/internals.d.ts +105 -94
  63. package/dist/internals.js +81 -81
  64. package/dist/internals.js.br +0 -0
  65. package/dist/internals.js.gz +0 -0
  66. package/dist/internals.mjs +2 -2
  67. package/dist/internals.mjs.br +0 -0
  68. package/dist/internals.mjs.gz +0 -0
  69. package/dist/pptx-viewer-canvas.css +1 -1
  70. package/dist/pptx-viewer-canvas.css.br +0 -0
  71. package/dist/pptx-viewer-canvas.css.gz +0 -0
  72. package/dist/pptx-viewer.css +1 -1
  73. package/dist/pptx-viewer.css.br +0 -0
  74. package/dist/pptx-viewer.css.gz +0 -0
  75. package/dist/viewer/index.d.ts +58 -24
  76. package/dist/viewer/index.js +23 -19
  77. package/dist/viewer/index.js.br +0 -0
  78. package/dist/viewer/index.js.gz +0 -0
  79. package/dist/viewer/index.mjs +3 -3
  80. package/dist/viewer/index.mjs.br +0 -0
  81. package/dist/viewer/index.mjs.gz +0 -0
  82. package/dist/{y-webrtc-2H7UYAQD.mjs → y-webrtc-HRGGVQZH.mjs} +1 -1
  83. package/dist/y-webrtc-HRGGVQZH.mjs.br +0 -0
  84. package/dist/y-webrtc-HRGGVQZH.mjs.gz +0 -0
  85. package/dist/{y-webrtc-DVPQNFLL.js → y-webrtc-QVVT5PCK.js} +124 -124
  86. package/dist/y-webrtc-QVVT5PCK.js.br +0 -0
  87. package/dist/y-webrtc-QVVT5PCK.js.gz +0 -0
  88. package/dist/{y-websocket-WORVRRQS.mjs → y-websocket-46M6UFI7.mjs} +1 -1
  89. package/dist/y-websocket-46M6UFI7.mjs.br +0 -0
  90. package/dist/y-websocket-46M6UFI7.mjs.gz +0 -0
  91. package/dist/{y-websocket-TN4OKPXQ.js → y-websocket-EDOMWMAD.js} +78 -78
  92. package/dist/y-websocket-EDOMWMAD.js.br +0 -0
  93. package/dist/y-websocket-EDOMWMAD.js.gz +0 -0
  94. package/package.json +3 -3
  95. package/dist/AiChatPanel-NKTBHT5B.mjs.br +0 -0
  96. package/dist/AiChatPanel-NKTBHT5B.mjs.gz +0 -0
  97. package/dist/AiChatPanel-S4WNQCY4.js.br +0 -0
  98. package/dist/AiChatPanel-S4WNQCY4.js.gz +0 -0
  99. package/dist/chunk-2ZXXHNNR.js.br +0 -0
  100. package/dist/chunk-2ZXXHNNR.js.gz +0 -0
  101. package/dist/chunk-3VVZGHHY.mjs.br +0 -0
  102. package/dist/chunk-3VVZGHHY.mjs.gz +0 -0
  103. package/dist/chunk-JAI42EXQ.js.br +0 -0
  104. package/dist/chunk-JAI42EXQ.js.gz +0 -0
  105. package/dist/chunk-ME5QRTVS.js.br +0 -0
  106. package/dist/chunk-ME5QRTVS.js.gz +0 -0
  107. package/dist/chunk-NUNDT6EL.js.br +0 -0
  108. package/dist/chunk-NUNDT6EL.js.gz +0 -0
  109. package/dist/chunk-PFV626ED.mjs.br +0 -0
  110. package/dist/chunk-PFV626ED.mjs.gz +0 -0
  111. package/dist/chunk-YKLL2Z33.mjs.br +0 -0
  112. package/dist/chunk-YKLL2Z33.mjs.gz +0 -0
  113. package/dist/chunk-Z62JFXMZ.mjs.br +0 -0
  114. package/dist/chunk-Z62JFXMZ.mjs.gz +0 -0
  115. package/dist/y-webrtc-2H7UYAQD.mjs.br +0 -0
  116. package/dist/y-webrtc-2H7UYAQD.mjs.gz +0 -0
  117. package/dist/y-webrtc-DVPQNFLL.js.br +0 -0
  118. package/dist/y-webrtc-DVPQNFLL.js.gz +0 -0
  119. package/dist/y-websocket-TN4OKPXQ.js.br +0 -0
  120. package/dist/y-websocket-TN4OKPXQ.js.gz +0 -0
  121. package/dist/y-websocket-WORVRRQS.mjs.br +0 -0
  122. package/dist/y-websocket-WORVRRQS.mjs.gz +0 -0
  123. package/dist/{chunk-NUNDT6EL.js → chunk-DDJ634MV.js} +436 -436
  124. package/dist/{chunk-3VVZGHHY.mjs → chunk-QIUK53JR.mjs} +436 -436
@@ -1,11 +1,10 @@
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, html2canvasCaptureScope, 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, 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, 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, useTranslation, 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-ZCGYPCZM.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
- 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';
6
+ import { PptxHandler, EncryptedFileError, hasShapeProperties, applyThemeToData, THEME_PRESETS, themeColorSchemesEqual, guidePxToEmu, hasTextProperties, isInkElement, duplicateSlide, buildThemeColorMap, reResolveSlideColors, reResolveElementColors, reResolveSlideFonts, reResolveElementFonts, isImageLikeElement, SvgExporter, getSubstituteFontFamily, elementActionToPptxAction, setSmartArtNodeStyle, updateSmartArtNodeText } from 'ooxml-core/pptx';
7
7
  import { translationsEn } from 'ooxml-ui/pptx/i18n';
8
- import { useTranslation } from 'react-i18next';
9
8
  import { flushSync } from 'react-dom';
10
9
  import { LuRotateCw } from 'react-icons/lu';
11
10
 
@@ -75,240 +74,59 @@ function useVirtualizedSlides({
75
74
  scrollToIndex
76
75
  };
77
76
  }
77
+ function useController(controller) {
78
+ return useSyncExternalStore(controller.subscribe, controller.getSnapshot, controller.getSnapshot);
79
+ }
80
+
81
+ // src/viewer/hooks/collaboration/collaboration-modules.ts
82
+ var collabModules = {
83
+ loadYjs: () => import('yjs'),
84
+ loadWebsocket: () => import('./y-websocket-46M6UFI7.mjs'),
85
+ loadWebrtc: () => import('./y-webrtc-HRGGVQZH.mjs')
86
+ };
87
+
88
+ // src/viewer/hooks/collaboration/useYjsProvider.ts
78
89
  function useYjsProvider({ config: inputConfig }) {
79
90
  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]
91
+ const [controller] = useState(
92
+ () => createCollaborationSessionController({ modules: collabModules, registerTeardown: false })
121
93
  );
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;
94
+ const state = useController(controller);
95
+ const signalingKey = config?.signaling?.join("\n");
96
+ useEffect(() => {
186
97
  if (!config) {
98
+ controller.stop();
187
99
  return;
188
100
  }
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
- }
101
+ void controller.start(config);
102
+ const disposeTeardown = registerCollaborationTeardown({
103
+ leave: () => controller.stop(),
104
+ rejoin: () => void controller.retry()
105
+ });
106
+ return () => {
107
+ disposeTeardown();
108
+ controller.stop();
109
+ };
288
110
  }, [
111
+ controller,
289
112
  config?.roomId,
290
113
  config?.serverUrl,
291
114
  config?.authToken,
292
115
  config?.transport,
293
- announceDeparture,
294
- initWebrtc,
295
- teardown
116
+ signalingKey,
117
+ Boolean(config)
296
118
  ]);
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 };
119
+ const session = state.active ? controller.getSession() : null;
120
+ return {
121
+ status: state.status,
122
+ awareness: session?.awareness ?? null,
123
+ doc: session?.doc ?? null,
124
+ clientId: state.clientId,
125
+ synced: state.synced,
126
+ retry: () => {
127
+ void controller.retry();
128
+ }
129
+ };
312
130
  }
313
131
  function usePresenceTracking({
314
132
  awareness,
@@ -757,15 +575,11 @@ function serializeHistoryDocument(snapshot) {
757
575
  )
758
576
  });
759
577
  }
760
- function recordExplicitSlideUpdate(past, future, previous, before, label, maxDepth) {
578
+ function recordExplicitSlideUpdate(history, previous, before, label) {
761
579
  if (previous && serializeHistoryDocument(previous) !== serializeHistoryDocument(before)) {
762
- past.push(cloneHistorySnapshot(previous));
580
+ history.record(cloneHistorySnapshot(previous), previous.actionLabel);
763
581
  }
764
- past.push({ ...cloneHistorySnapshot(before), actionLabel: label });
765
- while (past.length > Math.max(1, maxDepth)) {
766
- past.shift();
767
- }
768
- future.length = 0;
582
+ history.record({ ...cloneHistorySnapshot(before), actionLabel: label }, label);
769
583
  }
770
584
  var POLL_INTERVAL_MS = 16;
771
585
  function usePointerReleaseWakeup(hasActivePointerInteraction) {
@@ -801,7 +615,6 @@ function usePointerReleaseWakeup(hasActivePointerInteraction) {
801
615
  }
802
616
 
803
617
  // src/viewer/hooks/useEditorHistory.ts
804
- var DEFAULT_MAX_HISTORY_ENTRIES = 120;
805
618
  function useEditorHistory(input) {
806
619
  const {
807
620
  slides,
@@ -810,7 +623,7 @@ function useEditorHistory(input) {
810
623
  templateElementsBySlideId,
811
624
  loading,
812
625
  error,
813
- maxHistoryEntries = DEFAULT_MAX_HISTORY_ENTRIES,
626
+ maxHistoryEntries,
814
627
  hasActivePointerInteraction,
815
628
  pointerCommitNonce,
816
629
  onDirty,
@@ -821,30 +634,23 @@ function useEditorHistory(input) {
821
634
  setSelectedElementId,
822
635
  setSelectedElementIds
823
636
  } = input;
824
- const historyPastRef = useRef([]);
825
- const historyFutureRef = useRef([]);
637
+ const [history] = useState(() => createEditorHistoryController());
638
+ const historyState = useController(history);
826
639
  const lastHistorySnapshotRef = useRef(null);
827
640
  const lastHistorySerializedRef = useRef("");
828
641
  const lastCheapHashRef = useRef("");
829
642
  const isApplyingHistoryRef = useRef(false);
830
643
  const trackingHeldRef = useRef(false);
831
644
  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
645
  const [editCommitNonce, setEditCommitNonce] = useState(0);
837
646
  const { pointerReleaseNonce, deferUntilPointerReleased } = usePointerReleaseWakeup(
838
647
  hasActivePointerInteraction
839
648
  );
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
- }, []);
649
+ useEffect(() => {
650
+ if (maxHistoryEntries !== void 0) {
651
+ history.setMaxDepth(maxHistoryEntries);
652
+ }
653
+ }, [history, maxHistoryEntries]);
848
654
  const buildHistorySnapshot = useCallback(
849
655
  (actionLabel) => {
850
656
  return {
@@ -899,53 +705,40 @@ function useEditorHistory(input) {
899
705
  onDirtyRef.current?.();
900
706
  }, []);
901
707
  const handleUndo = useCallback(() => {
902
- const previousSnapshot = historyPastRef.current.pop();
903
- if (!previousSnapshot) {
708
+ if (!history.getSnapshot().canUndo) {
709
+ return;
710
+ }
711
+ const result = history.undo(buildHistorySnapshot());
712
+ if (!result) {
904
713
  return;
905
714
  }
906
- const currentSnapshot = buildHistorySnapshot(previousSnapshot.actionLabel);
907
- historyFutureRef.current.push(currentSnapshot);
908
715
  isApplyingHistoryRef.current = true;
909
- const nextSnapshot = cloneHistorySnapshot(previousSnapshot);
716
+ const nextSnapshot = cloneHistorySnapshot(result.snapshot);
910
717
  lastHistorySnapshotRef.current = cloneHistorySnapshot(nextSnapshot);
911
718
  lastHistorySerializedRef.current = serializeHistoryDocument(nextSnapshot);
912
719
  applyHistorySnapshot(nextSnapshot);
913
- updateHistoryAvailability();
914
720
  unlockHistoryTracking();
915
721
  markDirty();
916
- }, [
917
- applyHistorySnapshot,
918
- buildHistorySnapshot,
919
- markDirty,
920
- unlockHistoryTracking,
921
- updateHistoryAvailability
922
- ]);
722
+ }, [applyHistorySnapshot, buildHistorySnapshot, history, markDirty, unlockHistoryTracking]);
923
723
  const handleRedo = useCallback(() => {
924
- const nextSnapshot = historyFutureRef.current.pop();
925
- if (!nextSnapshot) {
724
+ if (!history.getSnapshot().canRedo) {
725
+ return;
726
+ }
727
+ const result = history.redo(buildHistorySnapshot());
728
+ if (!result) {
926
729
  return;
927
730
  }
928
- const currentSnapshot = buildHistorySnapshot(nextSnapshot.actionLabel);
929
- historyPastRef.current.push(currentSnapshot);
930
731
  isApplyingHistoryRef.current = true;
931
- const targetSnapshot = cloneHistorySnapshot(nextSnapshot);
732
+ const targetSnapshot = cloneHistorySnapshot(result.snapshot);
932
733
  lastHistorySnapshotRef.current = cloneHistorySnapshot(targetSnapshot);
933
734
  lastHistorySerializedRef.current = serializeHistoryDocument(targetSnapshot);
934
735
  applyHistorySnapshot(targetSnapshot);
935
- updateHistoryAvailability();
936
736
  unlockHistoryTracking();
937
737
  markDirty();
938
- }, [
939
- applyHistorySnapshot,
940
- buildHistorySnapshot,
941
- markDirty,
942
- unlockHistoryTracking,
943
- updateHistoryAvailability
944
- ]);
738
+ }, [applyHistorySnapshot, buildHistorySnapshot, history, markDirty, unlockHistoryTracking]);
945
739
  const resetHistory = useCallback(
946
740
  (initialSnapshot) => {
947
- historyPastRef.current = [];
948
- historyFutureRef.current = [];
741
+ history.clear();
949
742
  if (initialSnapshot) {
950
743
  const clonedInitial = cloneHistorySnapshot(initialSnapshot);
951
744
  lastHistorySnapshotRef.current = clonedInitial;
@@ -954,9 +747,8 @@ function useEditorHistory(input) {
954
747
  lastHistorySnapshotRef.current = null;
955
748
  lastHistorySerializedRef.current = "";
956
749
  }
957
- updateHistoryAvailability();
958
750
  },
959
- [updateHistoryAvailability]
751
+ [history]
960
752
  );
961
753
  useEffect(() => {
962
754
  return () => {
@@ -999,19 +791,13 @@ function useEditorHistory(input) {
999
791
  lastHistorySnapshotRef.current = snapshot;
1000
792
  lastHistorySerializedRef.current = serialized;
1001
793
  lastCheapHashRef.current = cheapHash;
1002
- updateHistoryAvailability();
1003
794
  return;
1004
795
  }
1005
796
  onDirtyRef.current?.();
1006
- historyPastRef.current.push(previousSnapshot);
1007
- while (historyPastRef.current.length > Math.max(1, maxHistoryEntries)) {
1008
- historyPastRef.current.shift();
1009
- }
1010
- historyFutureRef.current = [];
797
+ history.record(previousSnapshot, previousSnapshot.actionLabel);
1011
798
  lastHistorySnapshotRef.current = snapshot;
1012
799
  lastHistorySerializedRef.current = serialized;
1013
800
  lastCheapHashRef.current = cheapHash;
1014
- updateHistoryAvailability();
1015
801
  }, [
1016
802
  activeSlideIndex,
1017
803
  buildHistorySnapshot,
@@ -1021,30 +807,21 @@ function useEditorHistory(input) {
1021
807
  editCommitNonce,
1022
808
  error,
1023
809
  hasActivePointerInteraction,
810
+ history,
1024
811
  loading,
1025
- maxHistoryEntries,
1026
812
  pointerCommitNonce,
1027
813
  pointerReleaseNonce,
1028
- slides,
1029
- updateHistoryAvailability
814
+ slides
1030
815
  ]);
1031
816
  const commitSlides = (nextSlides, label, beforeSlides = slides) => {
1032
817
  const before = { ...buildHistorySnapshot(), slides: beforeSlides.map(cloneSlide) };
1033
818
  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
- );
819
+ recordExplicitSlideUpdate(history, lastHistorySnapshotRef.current, before, label);
1042
820
  lastHistorySnapshotRef.current = cloneHistorySnapshot(after);
1043
821
  lastHistorySerializedRef.current = serializeHistoryDocument(after);
1044
822
  isApplyingHistoryRef.current = false;
1045
823
  setSlides(nextSlides);
1046
824
  onDirtyRef.current?.();
1047
- updateHistoryAvailability();
1048
825
  };
1049
826
  const holdTracking = useCallback((held) => {
1050
827
  trackingHeldRef.current = held;
@@ -1052,10 +829,10 @@ function useEditorHistory(input) {
1052
829
  return {
1053
830
  commitSlides,
1054
831
  holdTracking,
1055
- canUndo,
1056
- canRedo,
1057
- undoLabel,
1058
- redoLabel,
832
+ canUndo: historyState.canUndo,
833
+ canRedo: historyState.canRedo,
834
+ undoLabel: historyState.undoLabel,
835
+ redoLabel: historyState.redoLabel,
1059
836
  handleUndo,
1060
837
  handleRedo,
1061
838
  resetHistory,
@@ -2198,9 +1975,6 @@ function executeSlideTransition(nextSlideIndex, deps) {
2198
1975
  }
2199
1976
 
2200
1977
  // 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
1978
  function useSlideNavigation(input) {
2205
1979
  const {
2206
1980
  slides,
@@ -2250,50 +2024,71 @@ function useSlideNavigation(input) {
2250
2024
  );
2251
2025
  const movePresentationSlide = useCallback(
2252
2026
  (direction, trigger = "explicit") => {
2253
- if (direction === 1 && playNextAnimationGroup()) {
2254
- return;
2255
- }
2256
- if (isClickAdvanceBlocked(slides[presentationSlideIndex], direction, trigger)) {
2027
+ const showOrder = visibleSlideIndexes.length > 0 ? visibleSlideIndexes : slides.map((_slide, slideIndex) => slideIndex);
2028
+ const current = presentationSlideIndex;
2029
+ const transitionTo = (index, options = {}) => executeSlideTransition(index, {
2030
+ slides,
2031
+ currentSlideIndex: current,
2032
+ onPlayActionSound,
2033
+ onStopActionSound,
2034
+ setPresentationSlideVisible,
2035
+ clearPresentationTimers,
2036
+ setPresentationSlideIndex,
2037
+ onSetActiveSlideIndex,
2038
+ seedSlideAnimations,
2039
+ startSlideAnimations,
2040
+ scheduleAutoAdvanceForSlide: rehearsing ? void 0 : scheduleAutoAdvanceForSlide,
2041
+ presentationTimersRef,
2042
+ setTransitionOverlay,
2043
+ // PowerPoint plays a slide's transition only when advancing into it,
2044
+ // and replays the LEAVING slide's transition in reverse when stepping
2045
+ // back (a morph glides its shapes back to where they came from).
2046
+ playTransition: true,
2047
+ ...options
2048
+ });
2049
+ if (direction === -1) {
2050
+ const step2 = resolveShowBackward({
2051
+ currentIndex: current,
2052
+ showOrder,
2053
+ endOfShow: false,
2054
+ seededCompleted: false
2055
+ });
2056
+ if (step2.kind === "go") {
2057
+ transitionTo(step2.index, { reverse: true, seedCompleted: true });
2058
+ }
2257
2059
  return;
2258
2060
  }
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,
2061
+ const step = resolveShowForward({
2062
+ currentIndex: current,
2063
+ currentSlide: slides[current],
2064
+ showOrder,
2065
+ endOfShow: false,
2066
+ fromClick: trigger === "click",
2067
+ loop: Boolean(loopContinuously) && !rehearsing,
2068
+ endWithBlackSlide: true,
2069
+ zoomExcursion: zoomExcursionRef.current,
2070
+ advanceBuild: playNextAnimationGroup
2071
+ });
2072
+ if (step.kind === "go") {
2073
+ if (step.zoomReturn) {
2074
+ zoomExcursionRef.current = step.zoomReturn.excursion;
2075
+ transitionTo(step.index, {
2279
2076
  scheduleAutoAdvanceForSlide,
2280
- presentationTimersRef,
2281
- setTransitionOverlay,
2282
- playTransition: true,
2283
- transitionOverride: buildZoomTransitionOverride(
2284
- consumedExcursion?.transitionDurationMs
2285
- )
2077
+ transitionOverride: step.zoomReturn.transitionOverride
2286
2078
  });
2287
2079
  return;
2288
2080
  }
2081
+ if (rehearsing) {
2082
+ recordCurrentSlideTime(current);
2083
+ }
2084
+ transitionTo(step.index);
2085
+ return;
2289
2086
  }
2290
- const availableSlideIndexes = visibleSlideIndexes.length > 0 ? visibleSlideIndexes : slides.map((_slide, slideIndex) => slideIndex);
2291
- if (availableSlideIndexes.length === 0) {
2087
+ if (step.kind !== "end-screen" && step.kind !== "exit") {
2292
2088
  return;
2293
2089
  }
2294
- const pastLastSlide = direction === 1 && !hasShowSlideAfter(presentationSlideIndex, availableSlideIndexes);
2295
- if (rehearsing && pastLastSlide) {
2296
- recordCurrentSlideTime(presentationSlideIndex);
2090
+ if (rehearsing) {
2091
+ recordCurrentSlideTime(current);
2297
2092
  try {
2298
2093
  if (document.fullscreenElement) {
2299
2094
  void document.exitFullscreen().catch(() => {
@@ -2305,41 +2100,7 @@ function useSlideNavigation(input) {
2305
2100
  setShowRehearsalSummary(true);
2306
2101
  return;
2307
2102
  }
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
- });
2103
+ onAdvancePastLastSlide?.();
2343
2104
  },
2344
2105
  [
2345
2106
  clearPresentationTimers,
@@ -4199,6 +3960,7 @@ function useViewerCoreState(_input) {
4199
3960
  const marqueeStateRef = useRef(null);
4200
3961
  const isDrawingRef = useRef(false);
4201
3962
  const justInteractedRef = useRef(false);
3963
+ const gestureControllerRef = useRef(null);
4202
3964
  const livePatcher = useMemo(() => createCollaborationLivePatcher(), []);
4203
3965
  useEffect(() => () => livePatcher.dispose(), [livePatcher]);
4204
3966
  const [mode, setMode] = useState("edit");
@@ -4215,10 +3977,11 @@ function useViewerCoreState(_input) {
4215
3977
  const [selectedElementId, setSelectedElementId] = useState(null);
4216
3978
  const [selectedElementIds, setSelectedElementIds] = useState([]);
4217
3979
  const [isDirty, setIsDirty] = useState(false);
3980
+ const autosaveDraftSinkRef = useRef(null);
4218
3981
  const publishInlineText = useCallback(
4219
3982
  (elementId, text) => {
4220
- publishLiveInlineText(
4221
- livePatcher,
3983
+ reportInlineTextDraft(
3984
+ { livePatcher, autosave: autosaveDraftSinkRef.current },
4222
3985
  slidesRef.current[activeSlideIndexRef.current],
4223
3986
  elementId,
4224
3987
  text
@@ -4284,6 +4047,7 @@ function useViewerCoreState(_input) {
4284
4047
  imageInputRef,
4285
4048
  mediaInputRef,
4286
4049
  activeSlideIndexRef,
4050
+ autosaveDraftSinkRef,
4287
4051
  ...inlineEditing,
4288
4052
  ...editPointsMode,
4289
4053
  dragStateRef,
@@ -4292,6 +4056,7 @@ function useViewerCoreState(_input) {
4292
4056
  marqueeStateRef,
4293
4057
  isDrawingRef,
4294
4058
  justInteractedRef,
4059
+ gestureControllerRef,
4295
4060
  livePatcher,
4296
4061
  mode,
4297
4062
  setMode,
@@ -4778,96 +4543,43 @@ function useLoadContent({
4778
4543
  return { handlerRef };
4779
4544
  }
4780
4545
  function useAutosave(input) {
4781
- const { isDirty, filePath, serializeSlides, enabled = true, getChangeSources } = input;
4546
+ const { isDirty, filePath, enabled = true } = input;
4782
4547
  const intervalMs = resolveAutosaveIntervalMs({
4783
4548
  hostIntervalMs: input.intervalMs,
4784
4549
  optionsIntervalSeconds: input.intervalSeconds
4785
4550
  });
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]);
4551
+ const live = useRef(input);
4552
+ live.current = input;
4553
+ const [controller] = useState(
4554
+ () => createAutosaveController({
4555
+ getActivation: () => ({ active: live.current.enabled ?? true }),
4556
+ getFilePath: () => live.current.filePath,
4557
+ getIntervalMs: () => resolveAutosaveIntervalMs({
4558
+ hostIntervalMs: live.current.intervalMs,
4559
+ optionsIntervalSeconds: live.current.intervalSeconds
4560
+ }),
4561
+ serialize: () => live.current.serializeSlides(),
4562
+ getStorage: () => live.current.storage,
4563
+ onSaved: (data) => live.current.onSaved?.(data),
4564
+ writeSnapshot: saveAutosaveSnapshot,
4565
+ // serializeSlides already overlays the live inline draft from its refs.
4566
+ getInlineEditingId: () => live.current.getInlineEditingId?.()
4567
+ })
4568
+ );
4569
+ if (input.inlineDraftSinkRef) {
4570
+ input.inlineDraftSinkRef.current = controller;
4571
+ }
4572
+ const { status } = useController(controller);
4810
4573
  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
- }, []);
4574
+ controller.refresh();
4575
+ }, [controller, enabled, filePath, intervalMs]);
4850
4576
  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]);
4577
+ controller.observe(isDirty, live.current.getChangeSources?.() ?? []);
4578
+ });
4579
+ useEffect(() => () => controller.markClean(), [controller]);
4868
4580
  return {
4869
- autosaveStatus,
4870
- triggerAutosave: () => doAutosave()
4581
+ autosaveStatus: status,
4582
+ triggerAutosave: () => controller.saveNow()
4871
4583
  };
4872
4584
  }
4873
4585
 
@@ -5382,45 +5094,238 @@ function syncSelectionHandleOverlay(elementNode, elementId, geometry) {
5382
5094
  }
5383
5095
  }
5384
5096
 
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);
5097
+ // src/viewer/hooks/canvas-gestures.ts
5098
+ var animationFrames = {
5099
+ request: (callback) => requestAnimationFrame(callback),
5100
+ cancel: (handle) => cancelAnimationFrame(handle)
5101
+ };
5102
+ function createCanvasGestures(getInput, frames = animationFrames) {
5103
+ let armed = null;
5104
+ let pending = null;
5105
+ let frameHandle = 0;
5106
+ let lastSnapLinesKey = "";
5107
+ const liveTarget = () => {
5108
+ const { livePatcher, editTemplateMode, activeSlide } = getInput();
5109
+ return livePatcher && !editTemplateMode && livePatcher.isActive() ? { patcher: livePatcher, slideId: activeSlide?.id } : void 0;
5110
+ };
5111
+ function showSnapLines(lines) {
5112
+ const next = lines.map((line) => ({
5113
+ axis: line.axis === "v" ? "x" : "y",
5114
+ position: line.position
5115
+ }));
5116
+ const key = JSON.stringify(next);
5117
+ if (key !== lastSnapLinesKey) {
5118
+ lastSnapLinesKey = key;
5119
+ getInput().setSnapLines(next);
5120
+ }
5391
5121
  }
5392
- const box = { x: resized.x, y: resized.y, width: resized.width, height: resized.height };
5393
- if (!snapToGrid) {
5394
- return box;
5122
+ function paintMove(drag, transform) {
5123
+ const start = drag.startPositionsById[transform.id];
5124
+ if (!start) {
5125
+ return;
5126
+ }
5127
+ drag.lastDx = transform.x - start.x;
5128
+ drag.lastDy = transform.y - start.y;
5129
+ const live = liveTarget();
5130
+ for (const [id, from] of Object.entries(drag.startPositionsById)) {
5131
+ const x = from.x + drag.lastDx;
5132
+ const y = from.y + drag.lastDy;
5133
+ const domEl = drag.domEls.get(id);
5134
+ if (domEl) {
5135
+ domEl.style.left = `${x}px`;
5136
+ domEl.style.top = `${y}px`;
5137
+ syncSelectionHandleOverlay(domEl, id, { x, y });
5138
+ }
5139
+ if (live) {
5140
+ publishLiveGeometry(live.patcher, live.slideId, id, { x, y });
5141
+ }
5142
+ }
5143
+ }
5144
+ function paintResize(resize, transform) {
5145
+ const { x, y } = transform;
5146
+ const width = Math.max(transform.width, MIN_ELEMENT_SIZE);
5147
+ const height = Math.max(transform.height, MIN_ELEMENT_SIZE);
5148
+ resize.lastX = x;
5149
+ resize.lastY = y;
5150
+ resize.lastWidth = transform.width;
5151
+ resize.lastHeight = transform.height;
5152
+ if (resize.domEl) {
5153
+ Object.assign(resize.domEl.style, {
5154
+ left: `${x}px`,
5155
+ top: `${y}px`,
5156
+ width: `${width}px`,
5157
+ height: `${height}px`
5158
+ });
5159
+ syncSelectionHandleOverlay(resize.domEl, resize.elementId, { x, y, width, height });
5160
+ }
5161
+ const live = liveTarget();
5162
+ if (live) {
5163
+ publishLiveGeometry(live.patcher, live.slideId, resize.elementId, { x, y, width, height });
5164
+ }
5165
+ }
5166
+ function paintPending() {
5167
+ const frame = pending;
5168
+ pending = null;
5169
+ frameHandle = 0;
5170
+ if (!frame || !armed) {
5171
+ return;
5172
+ }
5173
+ if (armed.kind === "move") {
5174
+ armed.drag.moved = true;
5175
+ showSnapLines(frame.lines);
5176
+ paintMove(armed.drag, frame.transform);
5177
+ } else {
5178
+ armed.resize.moved = true;
5179
+ paintResize(armed.resize, frame.transform);
5180
+ }
5181
+ }
5182
+ function flushFrame() {
5183
+ if (frameHandle !== 0) {
5184
+ frames.cancel(frameHandle);
5185
+ }
5186
+ paintPending();
5187
+ }
5188
+ const controller = createGestureController({
5189
+ getScale: () => getInput().editorScale,
5190
+ // Axis-aligned, as the canvas has always moved and resized (no rotation).
5191
+ getElementBox: (id) => {
5192
+ const el = getInput().elementLookup.get(id);
5193
+ return el ? { x: el.x, y: el.y, width: el.width, height: el.height } : void 0;
5194
+ },
5195
+ getSiblings: () => {
5196
+ const { editTemplateMode, templateElements, activeSlide } = getInput();
5197
+ const source = editTemplateMode ? templateElements : activeSlide?.elements ?? [];
5198
+ return source.map(({ id, x, y, width, height }) => ({ id, x, y, width, height }));
5199
+ },
5200
+ getSnapToGrid: () => getInput().snapToGrid,
5201
+ getGridSize: () => getInput().gridSpacingPx,
5202
+ getSnapToShape: () => getInput().snapToShape,
5203
+ getGuides: () => getInput().guides.map(({ axis, position }) => ({ axis, position })),
5204
+ getMinSize: () => MIN_ELEMENT_SIZE,
5205
+ getSnapExcludeIds: () => armed?.kind === "move" ? Object.keys(armed.drag.startPositionsById) : [],
5206
+ getStageOrigin: () => {
5207
+ const rect = getInput().canvasStageRef.current?.getBoundingClientRect();
5208
+ return { left: rect?.left ?? 0, top: rect?.top ?? 0 };
5209
+ },
5210
+ onStart: () => void 0,
5211
+ onPreview: (transform, lines) => {
5212
+ pending = { transform, lines };
5213
+ if (frameHandle === 0) {
5214
+ frameHandle = frames.request(paintPending);
5215
+ }
5216
+ },
5217
+ onEnd: (transform, moved) => {
5218
+ flushFrame();
5219
+ const ended = armed;
5220
+ armed = null;
5221
+ if (ended) {
5222
+ finishGesture(ended, moved ? transform : null);
5223
+ }
5224
+ }
5225
+ });
5226
+ function finishGesture(ended, transform) {
5227
+ const input = getInput();
5228
+ if (transform) {
5229
+ if (ended.kind === "move") {
5230
+ commitMove(input, ended.drag);
5231
+ } else {
5232
+ commitResize(input, ended.resize);
5233
+ }
5234
+ input.justInteractedRef.current = true;
5235
+ input.livePatcher?.flush();
5236
+ }
5237
+ input.dragStateRef.current = null;
5238
+ input.resizeStateRef.current = null;
5239
+ lastSnapLinesKey = "";
5240
+ input.setSnapLines([]);
5241
+ if (transform) {
5242
+ input.markDirty();
5243
+ }
5244
+ input.setPointerCommitNonce((n) => n + 1);
5395
5245
  }
5396
- return snapBoxToGrid(box, handle, gridSpacingPx, MIN_ELEMENT_SIZE);
5246
+ return {
5247
+ begin(kind, id, pointer) {
5248
+ if (controller.isActive()) {
5249
+ return;
5250
+ }
5251
+ const input = getInput();
5252
+ const drag = input.dragStateRef.current;
5253
+ const resize = input.resizeStateRef.current;
5254
+ armed = kind === "move" && drag ? { kind, drag } : kind === "resize" && resize ? { kind, resize } : null;
5255
+ if (!armed) {
5256
+ return;
5257
+ }
5258
+ lastSnapLinesKey = "";
5259
+ const handle = armed.kind === "resize" ? armed.resize.handle : void 0;
5260
+ controller.begin(kind, id, pointer, handle);
5261
+ if (!controller.isActive()) {
5262
+ armed = null;
5263
+ }
5264
+ },
5265
+ isActive: () => controller.isActive(),
5266
+ dispose: () => {
5267
+ if (frameHandle !== 0) {
5268
+ frames.cancel(frameHandle);
5269
+ frameHandle = 0;
5270
+ }
5271
+ pending = null;
5272
+ armed = null;
5273
+ controller.dispose();
5274
+ }
5275
+ };
5397
5276
  }
5398
- function resolveLiveTarget(input) {
5399
- const { livePatcher, editTemplateMode, activeSlide } = input;
5400
- if (!livePatcher || editTemplateMode || !livePatcher.isActive()) {
5401
- return void 0;
5277
+ function commitMove(input, drag) {
5278
+ const { lastDx: dx, lastDy: dy } = drag;
5279
+ const movedIds = new Set(Object.keys(drag.startPositionsById));
5280
+ const moveElements = (elements) => {
5281
+ const moved = elements.map((el) => {
5282
+ const start = drag.startPositionsById[el.id];
5283
+ return start ? { ...el, x: start.x + dx, y: start.y + dy } : el;
5284
+ });
5285
+ return applyReroutedConnectors(moved, rerouteConnectorsForMovedElements(moved, movedIds));
5286
+ };
5287
+ if (input.editTemplateMode) {
5288
+ const slideId = input.activeSlide?.id;
5289
+ if (slideId) {
5290
+ input.setTemplateElementsBySlideId((prev) => ({
5291
+ ...prev,
5292
+ [slideId]: moveElements(prev[slideId] ?? [])
5293
+ }));
5294
+ }
5295
+ return;
5402
5296
  }
5403
- return { patcher: livePatcher, slideId: activeSlide?.id };
5297
+ input.updateSlides(
5298
+ (prev) => prev.map(
5299
+ (s, i) => i !== input.activeSlideIndex ? s : { ...s, elements: moveElements(s.elements) }
5300
+ )
5301
+ );
5404
5302
  }
5405
- function processPointerMove(e, input, tracker) {
5303
+ function commitResize(input, resize) {
5304
+ input.updateElementById(resize.elementId, {
5305
+ x: resize.lastX,
5306
+ y: resize.lastY,
5307
+ width: resize.lastWidth,
5308
+ height: resize.lastHeight
5309
+ });
5310
+ const movedIds = /* @__PURE__ */ new Set([resize.elementId]);
5311
+ input.updateSlides(
5312
+ (prev) => prev.map((s, i) => {
5313
+ if (i !== input.activeSlideIndex) {
5314
+ return s;
5315
+ }
5316
+ const rerouted = rerouteConnectorsForMovedElements(s.elements, movedIds);
5317
+ return rerouted.length === 0 ? s : { ...s, elements: applyReroutedConnectors(s.elements, rerouted) };
5318
+ })
5319
+ );
5320
+ }
5321
+ function processPointerMove(e, input) {
5406
5322
  const {
5407
5323
  editorScale,
5408
5324
  canvasStageRef,
5409
5325
  canvasSize,
5410
- snapToGrid,
5411
- snapToShape,
5412
- gridSpacingPx,
5413
- editTemplateMode,
5414
- templateElements,
5415
- activeSlide,
5416
- guides,
5417
- elementLookup,
5418
5326
  marqueeStateRef,
5419
- dragStateRef,
5420
- resizeStateRef,
5421
5327
  shapeAdjustmentDragStateRef,
5422
5328
  setMarqueeSelectionState,
5423
- setSnapLines,
5424
5329
  updateElementById
5425
5330
  } = input;
5426
5331
  const marquee = marqueeStateRef.current;
@@ -5435,31 +5340,6 @@ function processPointerMove(e, input, tracker) {
5435
5340
  );
5436
5341
  return;
5437
5342
  }
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
5343
  const adj = shapeAdjustmentDragStateRef.current;
5464
5344
  if (adj) {
5465
5345
  processAdjustmentMove(e, adj, editorScale, updateElementById);
@@ -5475,129 +5355,6 @@ function processMarqueeMove(e, marquee, canvasStageRef, canvasSize, editorScale,
5475
5355
  marquee.currentY = Math.max(0, Math.min(canvasSize.height, (e.clientY - rect.top) / editorScale));
5476
5356
  setMarqueeSelectionState({ ...marquee });
5477
5357
  }
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
5358
  function processAdjustmentMove(e, adj, editorScale, updateElementById) {
5602
5359
  const dx = (e.clientX - adj.startClientX) / editorScale;
5603
5360
  const dy = (e.clientY - adj.startClientY) / editorScale;
@@ -5616,26 +5373,20 @@ function processPointerUp(input) {
5616
5373
  editTemplateMode,
5617
5374
  templateElements,
5618
5375
  activeSlide,
5619
- activeSlideIndex,
5620
5376
  marqueeStateRef,
5621
5377
  dragStateRef,
5622
5378
  resizeStateRef,
5623
5379
  shapeAdjustmentDragStateRef,
5624
5380
  justInteractedRef,
5381
+ gestureRef,
5625
5382
  setMarqueeSelectionState,
5626
- setSnapLines,
5627
- setTemplateElementsBySlideId,
5628
5383
  setPointerCommitNonce,
5629
5384
  applySelection,
5630
5385
  clearSelection,
5631
- updateSlides,
5632
- updateElementById,
5633
5386
  markDirty,
5634
5387
  livePatcher
5635
5388
  } = input;
5636
5389
  const marquee = marqueeStateRef.current;
5637
- const drag = dragStateRef.current;
5638
- const rs = resizeStateRef.current;
5639
5390
  const adj = shapeAdjustmentDragStateRef.current;
5640
5391
  if (marquee) {
5641
5392
  commitMarquee(
@@ -5646,62 +5397,22 @@ function processPointerUp(input) {
5646
5397
  applySelection,
5647
5398
  clearSelection
5648
5399
  );
5649
- marqueeStateRef.current = null;
5650
- setMarqueeSelectionState(null);
5651
- }
5652
- if (drag?.moved) {
5653
- commitDrag(
5654
- drag,
5655
- editTemplateMode,
5656
- activeSlide,
5657
- activeSlideIndex,
5658
- setTemplateElementsBySlideId,
5659
- updateSlides
5660
- );
5661
5400
  }
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
- );
5401
+ if (!gestureRef?.current?.isActive()) {
5402
+ dragStateRef.current = null;
5403
+ resizeStateRef.current = null;
5687
5404
  }
5688
- const wasMoved = drag?.moved || rs?.moved || adj?.moved;
5689
- if (wasMoved) {
5405
+ if (adj?.moved) {
5690
5406
  justInteractedRef.current = true;
5691
- }
5692
- if (wasMoved) {
5693
5407
  livePatcher?.flush();
5694
5408
  }
5695
5409
  marqueeStateRef.current = null;
5696
- dragStateRef.current = null;
5697
- resizeStateRef.current = null;
5698
5410
  shapeAdjustmentDragStateRef.current = null;
5699
5411
  setMarqueeSelectionState(null);
5700
- setSnapLines([]);
5701
- if (wasMoved) {
5412
+ if (adj?.moved) {
5702
5413
  markDirty();
5703
5414
  }
5704
- if (drag || rs || adj || marquee) {
5415
+ if (adj || marquee) {
5705
5416
  setPointerCommitNonce((n) => n + 1);
5706
5417
  }
5707
5418
  }
@@ -5721,35 +5432,6 @@ function commitMarquee(marquee, editTemplateMode, templateElements, activeSlide,
5721
5432
  clearSelection();
5722
5433
  }
5723
5434
  }
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
5435
 
5754
5436
  // src/viewer/hooks/usePointerHandlers.ts
5755
5437
  function usePointerHandlers(input) {
@@ -5759,41 +5441,42 @@ function usePointerHandlers(input) {
5759
5441
  canvasSize,
5760
5442
  activeSlide,
5761
5443
  activeSlideIndex,
5762
- gridSpacingPx,
5763
- dragStateRef,
5764
- resizeStateRef,
5765
5444
  shapeAdjustmentDragStateRef,
5766
5445
  marqueeStateRef,
5767
5446
  editTemplateMode,
5768
- snapToGrid,
5769
- snapToShape,
5770
- guides,
5771
5447
  templateElements,
5772
- elementLookup,
5773
5448
  setMarqueeSelectionState,
5774
- setSnapLines,
5775
- setTemplateElementsBySlideId,
5776
5449
  setPointerCommitNonce,
5777
5450
  applySelection,
5778
5451
  clearSelection,
5779
- updateSlides,
5780
5452
  updateElementById,
5781
5453
  markDirty,
5782
- livePatcher
5454
+ livePatcher,
5455
+ gestureRef
5783
5456
  } = input;
5457
+ const inputRef = useRef(input);
5458
+ inputRef.current = input;
5459
+ const gestures = useMemo(() => createCanvasGestures(() => inputRef.current), []);
5784
5460
  useEffect(() => {
5785
- const tracker = {
5786
- rafId: 0,
5787
- pendingMoveEvent: null,
5788
- lastSnapLinesKey: ""
5461
+ if (gestureRef) {
5462
+ gestureRef.current = gestures;
5463
+ }
5464
+ return () => {
5465
+ gestures.dispose();
5466
+ if (gestureRef?.current === gestures) {
5467
+ gestureRef.current = null;
5468
+ }
5789
5469
  };
5470
+ }, [gestures, gestureRef]);
5471
+ useEffect(() => {
5472
+ const tracker = { rafId: 0, pendingMoveEvent: null };
5790
5473
  const handlePointerMove = (e) => {
5791
5474
  tracker.pendingMoveEvent = e;
5792
5475
  if (tracker.rafId === 0) {
5793
5476
  tracker.rafId = requestAnimationFrame(() => {
5794
5477
  tracker.rafId = 0;
5795
5478
  if (tracker.pendingMoveEvent) {
5796
- processPointerMove(tracker.pendingMoveEvent, input, tracker);
5479
+ processPointerMove(tracker.pendingMoveEvent, input);
5797
5480
  tracker.pendingMoveEvent = null;
5798
5481
  }
5799
5482
  });
@@ -5821,31 +5504,22 @@ function usePointerHandlers(input) {
5821
5504
  }, [
5822
5505
  editorScale,
5823
5506
  canvasStageRef,
5824
- dragStateRef,
5825
- resizeStateRef,
5826
5507
  shapeAdjustmentDragStateRef,
5827
5508
  marqueeStateRef,
5828
5509
  editTemplateMode,
5829
- snapToGrid,
5830
- snapToShape,
5831
- guides,
5832
5510
  templateElements,
5833
5511
  setMarqueeSelectionState,
5834
- setSnapLines,
5835
- elementLookup,
5836
- setTemplateElementsBySlideId,
5837
5512
  setPointerCommitNonce,
5838
5513
  activeSlide,
5839
5514
  activeSlideIndex,
5840
5515
  canvasSize.width,
5841
5516
  canvasSize.height,
5842
- gridSpacingPx,
5843
5517
  applySelection,
5844
5518
  clearSelection,
5845
- updateSlides,
5846
5519
  updateElementById,
5847
5520
  markDirty,
5848
- livePatcher
5521
+ livePatcher,
5522
+ gestureRef
5849
5523
  ]);
5850
5524
  }
5851
5525
  function mouseDownStartsSelectionDrag(event) {
@@ -5871,6 +5545,7 @@ function useCanvasInteractions(input) {
5871
5545
  shapeAdjustmentDragStateRef,
5872
5546
  marqueeStateRef,
5873
5547
  justInteractedRef,
5548
+ gestureRef,
5874
5549
  setInlineEditingElementId,
5875
5550
  setInlineEditingText,
5876
5551
  setContextMenuState,
@@ -5896,25 +5571,7 @@ function useCanvasInteractions(input) {
5896
5571
  const scale = editorScale || 1;
5897
5572
  return rect ? { x: (e.clientX - rect.left) / scale, y: (e.clientY - rect.top) / scale } : null;
5898
5573
  };
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
- };
5574
+ const resolvePressTarget = (elementId, e) => slideElements ? resolveGroupPressTarget(slideElements, elementId, slidePoint(e), effectiveSelectedIds) : elementId;
5918
5575
  const handleInlineEditCommit = () => {
5919
5576
  const editId = inlineEditingElementId;
5920
5577
  if (!editId) {
@@ -6014,9 +5671,7 @@ function useCanvasInteractions(input) {
6014
5671
  }
6015
5672
  };
6016
5673
  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;
5674
+ const elementId = mode === "present" || !slideElements ? pressedId : resolveGroupDoubleClickTarget(slideElements, pressedId, slidePoint(e));
6020
5675
  if (elementId !== pressedId) {
6021
5676
  ops.applySelection(elementId);
6022
5677
  }
@@ -6079,6 +5734,7 @@ function useCanvasInteractions(input) {
6079
5734
  lastDy: 0
6080
5735
  };
6081
5736
  setSnapLines([]);
5737
+ gestureRef?.current?.begin("move", movableIds[0], e);
6082
5738
  };
6083
5739
  const handleElementContextMenu = (elementId, e) => {
6084
5740
  if (mode === "present") {
@@ -6151,6 +5807,7 @@ function useCanvasInteractions(input) {
6151
5807
  lastWidth: el.width,
6152
5808
  lastHeight: el.height
6153
5809
  };
5810
+ gestureRef?.current?.begin("resize", elementId, e);
6154
5811
  };
6155
5812
  const handleRotate = (elementId, rotationDeg) => {
6156
5813
  const el = elementLookup.get(elementId);
@@ -6656,11 +6313,11 @@ function useInsertElements(input) {
6656
6313
  id: generateElementId()
6657
6314
  });
6658
6315
  };
6659
- const handleAddChart = (chartKind) => {
6316
+ const handleAddChart = (chartKind, options) => {
6660
6317
  if (!activeSlide) {
6661
6318
  return;
6662
6319
  }
6663
- addElement(createDefaultChartElement(chartKind));
6320
+ addElement(createDefaultChartElement(chartKind, void 0, options));
6664
6321
  };
6665
6322
  const structured = createStructuredElementHandlers({
6666
6323
  activeSlide,
@@ -8715,8 +8372,19 @@ function useThemeHandlers(input) {
8715
8372
  return;
8716
8373
  }
8717
8374
  await handler.updateThemeFontScheme(fontScheme);
8375
+ const previousFonts = theme?.fontScheme;
8376
+ bumpHistory();
8377
+ setSlides((prev) => reResolveSlideFonts(prev, previousFonts, fontScheme));
8378
+ setTemplateElementsBySlideId(
8379
+ (prev) => Object.fromEntries(
8380
+ Object.entries(prev).map(([slideId, elements]) => [
8381
+ slideId,
8382
+ reResolveElementFonts(elements, previousFonts, fontScheme)
8383
+ ])
8384
+ )
8385
+ );
8718
8386
  setTheme((prev) => prev ? { ...prev, fontScheme } : { fontScheme });
8719
- await refreshContentAfterThemeChange();
8387
+ history.markDirty();
8720
8388
  };
8721
8389
  const handleUpdateThemeName = async (name) => {
8722
8390
  const handler = handlerRef.current;
@@ -8738,20 +8406,7 @@ function useThemeHandlers(input) {
8738
8406
  }
8739
8407
  await refreshContentAfterThemeChange();
8740
8408
  };
8741
- const handleApplyThemeData = async (colorScheme, fontScheme, themeName) => {
8742
- const handler = handlerRef.current;
8743
- if (!handler) {
8744
- return;
8745
- }
8746
- await handler.applyTheme(colorScheme, fontScheme, themeName);
8747
- setTheme((prev) => ({
8748
- ...prev,
8749
- colorScheme,
8750
- fontScheme,
8751
- ...themeName && { name: themeName }
8752
- }));
8753
- await refreshContentAfterThemeChange();
8754
- };
8409
+ const handleApplyThemeData = (colorScheme, fontScheme, themeName) => applyThemeEditorEdit(input, { colorScheme, fontScheme, name: themeName ?? theme?.name ?? "" });
8755
8410
  const handleSetTemplateBackground = (path, backgroundColor) => {
8756
8411
  const handler = handlerRef.current;
8757
8412
  if (!handler) {
@@ -9105,6 +8760,7 @@ function useViewerDialogs(input) {
9105
8760
  embeddedFontFamilies
9106
8761
  } = input;
9107
8762
  const [isSmartArtDialogOpen, setIsSmartArtDialogOpen] = useState(false);
8763
+ const [isChartDialogOpen, setIsChartDialogOpen] = useState(false);
9108
8764
  const [isEquationDialogOpen, setIsEquationDialogOpen] = useState(false);
9109
8765
  const [isHyperlinkDialogOpen, setIsHyperlinkDialogOpen] = useState(false);
9110
8766
  const [isPasswordDialogOpen, setIsPasswordDialogOpen] = useState(false);
@@ -9249,6 +8905,8 @@ function useViewerDialogs(input) {
9249
8905
  return {
9250
8906
  isSmartArtDialogOpen,
9251
8907
  setIsSmartArtDialogOpen,
8908
+ isChartDialogOpen,
8909
+ setIsChartDialogOpen,
9252
8910
  isEquationDialogOpen,
9253
8911
  setIsEquationDialogOpen,
9254
8912
  isHyperlinkDialogOpen,
@@ -9694,84 +9352,35 @@ function useSerialize(input) {
9694
9352
  }
9695
9353
  function useRecoveryDetection(input) {
9696
9354
  const { filePath, fileName, loading, error, slideCount, autosaveAllowed = true } = input;
9697
- const recoveryCheckedRef = useRef(false);
9698
- const recordRef = useRef(null);
9699
- const actionPendingRef = useRef(false);
9700
- const [prompt, setPrompt] = useState(null);
9701
- const [discarding, setDiscarding] = useState(false);
9702
- const onRestore = input.onRestore;
9703
- const storageRef = useRef(input.storage);
9704
- storageRef.current = input.storage;
9705
- const onOfferedRef = useRef(input.onOffered);
9706
- useEffect(() => {
9707
- onOfferedRef.current = input.onOffered;
9708
- }, [input.onOffered]);
9709
- const openVersionHistory = input.openVersionHistory;
9355
+ const live = useRef(input);
9356
+ live.current = input;
9357
+ const [controller] = useState(
9358
+ () => createAutosaveRecoveryController({
9359
+ getStorage: () => live.current.storage,
9360
+ load: (bytes) => live.current.onRestore?.(bytes),
9361
+ onOffered: (record) => {
9362
+ live.current.onOffered?.(record);
9363
+ live.current.openVersionHistory?.();
9364
+ },
9365
+ probe: probeAutosaveRecovery,
9366
+ discardRecord: discardAutosaveRecovery
9367
+ })
9368
+ );
9369
+ const { prompt, discarding } = useController(controller);
9710
9370
  useEffect(() => {
9711
- if (!shouldProbeAutosaveRecovery({
9712
- alreadyChecked: recoveryCheckedRef.current,
9713
- filePath,
9714
- loading,
9715
- error,
9716
- slideCount,
9717
- autosaveAllowed
9718
- })) {
9719
- return;
9720
- }
9721
- recoveryCheckedRef.current = true;
9722
- void (async () => {
9723
- const offer = await probeAutosaveRecovery(
9724
- filePath,
9725
- Date.now(),
9726
- fileName,
9727
- storageRef.current
9728
- );
9729
- if (!offer) {
9730
- return;
9731
- }
9732
- recordRef.current = offer.record;
9733
- setPrompt(offer.prompt);
9734
- onOfferedRef.current?.(offer.record);
9735
- openVersionHistory?.();
9736
- })();
9737
- }, [filePath, fileName, loading, error, slideCount, autosaveAllowed, openVersionHistory]);
9738
- const restore = useCallback(() => {
9739
- if (actionPendingRef.current) {
9740
- return;
9741
- }
9742
- actionPendingRef.current = true;
9743
- const record = recordRef.current;
9744
- setPrompt(null);
9745
- if (record && onRestore) {
9746
- onRestore(acceptAutosaveRecovery(record));
9747
- acknowledgeAutosaveRecovery(record);
9748
- }
9749
- recordRef.current = null;
9750
- }, [onRestore]);
9751
- const discard = useCallback(() => {
9752
- if (actionPendingRef.current) {
9753
- return;
9754
- }
9755
- const record = recordRef.current;
9756
- if (!record) {
9757
- setPrompt(null);
9758
- return;
9371
+ controller.check({ filePath, fileName, loading, error, slideCount, autosaveAllowed });
9372
+ }, [controller, filePath, fileName, loading, error, slideCount, autosaveAllowed]);
9373
+ return {
9374
+ prompt,
9375
+ discarding,
9376
+ restore: () => {
9377
+ void controller.restore().catch(() => {
9378
+ });
9379
+ },
9380
+ discard: () => {
9381
+ void controller.discard();
9759
9382
  }
9760
- actionPendingRef.current = true;
9761
- setDiscarding(true);
9762
- void (async () => {
9763
- try {
9764
- await discardAutosaveRecovery(record, storageRef.current);
9765
- recordRef.current = null;
9766
- setPrompt(null);
9767
- } catch {
9768
- } finally {
9769
- actionPendingRef.current = false;
9770
- setDiscarding(false);
9771
- }
9772
- })();
9773
- }, []);
9774
- return { prompt, restore, discard, discarding };
9383
+ };
9775
9384
  }
9776
9385
  function usePresentationSetup(input) {
9777
9386
  const {
@@ -10245,6 +9854,7 @@ function useEditorOperations(input) {
10245
9854
  shapeAdjustmentDragStateRef: state.shapeAdjustmentDragStateRef,
10246
9855
  marqueeStateRef: state.marqueeStateRef,
10247
9856
  justInteractedRef: state.justInteractedRef,
9857
+ gestureRef: state.gestureControllerRef,
10248
9858
  setInlineEditingElementId: state.setInlineEditingElementId,
10249
9859
  setInlineEditingText: state.setInlineEditingText,
10250
9860
  setContextMenuState: state.setContextMenuState,
@@ -10777,6 +10387,8 @@ function useContentLifecycle(input) {
10777
10387
  // deck it has already snapshotted. The two refs are read by `.current`
10778
10388
  // because an inline edit in progress lives there and nowhere else yet.
10779
10389
  onSaved: input.onAutosave,
10390
+ inlineDraftSinkRef: state.autosaveDraftSinkRef,
10391
+ getInlineEditingId: () => state.inlineEditingElementIdRef.current,
10780
10392
  getChangeSources: () => [
10781
10393
  slides,
10782
10394
  state.templateElementsBySlideId,
@@ -10806,138 +10418,27 @@ function useKeyboardShortcuts(input) {
10806
10418
  inputRef.current = input;
10807
10419
  const handleKeyDown = useCallback((e) => {
10808
10420
  const current = inputRef.current;
10809
- const startAction = current.customization && !isFeatureEnabled(current.customization, "presentMode") ? null : mapSlideShowStartKey(e, { isPresenting: current.mode === "present" });
10810
- if (startAction !== null) {
10811
- e.preventDefault();
10812
- if (startAction === "fromBeginning") {
10813
- current.onStartShowFromBeginning();
10814
- } else {
10815
- current.onStartShowFromCurrent();
10816
- }
10817
- return;
10818
- }
10819
- const { action, dx, dy } = mapCustomizedEditorKey(
10421
+ dispatchEditorKey(
10820
10422
  e,
10821
10423
  {
10822
10424
  canEdit: current.canEdit,
10823
- canPaste: current.canPaste,
10824
- // The Slide Master view is an editing surface, not a viewing one.
10825
- // Gating on `mode !== 'edit'` alone made `mapEditorKey` return
10826
- // NO_ACTION there, so Delete, the arrow-key nudges and the clipboard
10827
- // keys were all inert over a master shape even though the write path
10828
- // behind them routes to the owning part correctly. Svelte and
10829
- // vanilla, which render the master into their ordinary editable
10830
- // stage, never had the gap.
10425
+ ...current.canPaste === void 0 ? {} : { canPaste: current.canPaste },
10426
+ // The Slide Master view is an editing surface, not a viewing one, so
10427
+ // only the other modes count as "not editing" for the editor keymap.
10831
10428
  isPresenting: current.mode !== "edit" && current.mode !== "master",
10429
+ // F5 is only inert while the show itself runs: a read-only viewer can
10430
+ // still start a show (it has a "From Beginning" button too).
10431
+ isShowRunning: current.mode === "present",
10432
+ canStartShow: !current.customization || isFeatureEnabled(current.customization, "presentMode"),
10832
10433
  hasSelection: current.hasSelection,
10833
10434
  isEditingText: Boolean(
10834
10435
  current.inlineEditingElementId || current.tableEditorState?.isEditing
10835
10436
  ),
10836
10437
  isDrawing: current.activeTool !== "select",
10837
- isTextInputTarget: isEditorTextInputTarget(e.target),
10838
- isControlTarget: isEditorControlTarget(e.target)
10438
+ keyboard: current.customization?.keyboard
10839
10439
  },
10840
- current.customization?.keyboard
10440
+ toEditorKeyHandlers(current)
10841
10441
  );
10842
- if (action === null) {
10843
- return;
10844
- }
10845
- e.preventDefault();
10846
- switch (action) {
10847
- case "escape":
10848
- current.onEscape();
10849
- break;
10850
- case "delete":
10851
- current.onDelete();
10852
- break;
10853
- case "undo":
10854
- current.onUndo();
10855
- break;
10856
- case "redo":
10857
- current.onRedo();
10858
- break;
10859
- case "copy":
10860
- current.onCopy();
10861
- break;
10862
- case "cut":
10863
- current.onCut();
10864
- break;
10865
- case "paste":
10866
- current.onPaste();
10867
- break;
10868
- case "duplicate":
10869
- current.onDuplicate();
10870
- break;
10871
- case "selectAll":
10872
- current.onSelectAll();
10873
- break;
10874
- case "group":
10875
- current.onGroup?.();
10876
- break;
10877
- case "ungroup":
10878
- current.onUngroup?.();
10879
- break;
10880
- case "toggleShortcuts":
10881
- current.onToggleShortcuts?.();
10882
- break;
10883
- case "find":
10884
- current.onFind?.();
10885
- break;
10886
- case "nudge":
10887
- current.onNudge(dx ?? 0, dy ?? 0);
10888
- break;
10889
- case "prevSlide":
10890
- current.onPrevSlide?.();
10891
- break;
10892
- case "nextSlide":
10893
- current.onNextSlide?.();
10894
- break;
10895
- case "alignLeft":
10896
- current.onAlignLeft?.();
10897
- break;
10898
- case "alignCenter":
10899
- current.onAlignCenter?.();
10900
- break;
10901
- case "alignRight":
10902
- current.onAlignRight?.();
10903
- break;
10904
- case "alignJustify":
10905
- current.onAlignJustify?.();
10906
- break;
10907
- case "increaseFontSize":
10908
- current.onIncreaseFontSize?.();
10909
- break;
10910
- case "decreaseFontSize":
10911
- current.onDecreaseFontSize?.();
10912
- break;
10913
- case "copyFormat":
10914
- current.onCopyFormat?.();
10915
- break;
10916
- case "pasteFormat":
10917
- current.onPasteFormat?.();
10918
- break;
10919
- case "newSlide":
10920
- current.onNewSlide?.();
10921
- break;
10922
- case "hyperlink":
10923
- current.onHyperlink?.();
10924
- break;
10925
- case "findReplace":
10926
- current.onFindReplace?.();
10927
- break;
10928
- case "clearFormatting":
10929
- current.onClearFormatting?.();
10930
- break;
10931
- case "cycleSelectionNext":
10932
- current.onCycleSelectionNext?.();
10933
- break;
10934
- case "cycleSelectionPrev":
10935
- current.onCycleSelectionPrev?.();
10936
- break;
10937
- case "pasteSpecial":
10938
- current.onPasteSpecial?.();
10939
- break;
10940
- }
10941
10442
  }, []);
10942
10443
  useEffect(() => {
10943
10444
  window.addEventListener("keydown", handleKeyDown);
@@ -10946,6 +10447,43 @@ function useKeyboardShortcuts(input) {
10946
10447
  };
10947
10448
  }, [handleKeyDown]);
10948
10449
  }
10450
+ function toEditorKeyHandlers(current) {
10451
+ return {
10452
+ startShowFromBeginning: current.onStartShowFromBeginning,
10453
+ startShowFromCurrent: current.onStartShowFromCurrent,
10454
+ escape: current.onEscape,
10455
+ delete: current.onDelete,
10456
+ undo: current.onUndo,
10457
+ redo: current.onRedo,
10458
+ copy: current.onCopy,
10459
+ cut: current.onCut,
10460
+ paste: current.onPaste,
10461
+ duplicate: current.onDuplicate,
10462
+ selectAll: current.onSelectAll,
10463
+ group: current.onGroup,
10464
+ ungroup: current.onUngroup,
10465
+ toggleShortcuts: current.onToggleShortcuts,
10466
+ find: current.onFind,
10467
+ nudge: current.onNudge,
10468
+ prevSlide: current.onPrevSlide,
10469
+ nextSlide: current.onNextSlide,
10470
+ alignLeft: current.onAlignLeft,
10471
+ alignCenter: current.onAlignCenter,
10472
+ alignRight: current.onAlignRight,
10473
+ alignJustify: current.onAlignJustify,
10474
+ increaseFontSize: current.onIncreaseFontSize,
10475
+ decreaseFontSize: current.onDecreaseFontSize,
10476
+ copyFormat: current.onCopyFormat,
10477
+ pasteFormat: current.onPasteFormat,
10478
+ newSlide: current.onNewSlide,
10479
+ hyperlink: current.onHyperlink,
10480
+ findReplace: current.onFindReplace,
10481
+ clearFormatting: current.onClearFormatting,
10482
+ cycleSelectionNext: current.onCycleSelectionNext,
10483
+ cycleSelectionPrev: current.onCycleSelectionPrev,
10484
+ pasteSpecial: current.onPasteSpecial
10485
+ };
10486
+ }
10949
10487
  var CLEAR_FORMATTING_PATCH = {
10950
10488
  bold: false,
10951
10489
  italic: false,
@@ -11211,7 +10749,8 @@ function useViewerIntegration(input) {
11211
10749
  updateSlides: editorOps.ops.updateSlides,
11212
10750
  updateElementById: editorOps.ops.updateElementById,
11213
10751
  markDirty: history.markDirty,
11214
- livePatcher: state.livePatcher
10752
+ livePatcher: state.livePatcher,
10753
+ gestureRef: state.gestureControllerRef
11215
10754
  });
11216
10755
  const [isEncryptedDialogOpen, setIsEncryptedDialogOpen] = useState(false);
11217
10756
  const [loadVersion, setLoadVersion] = useState(0);
@@ -12648,6 +12187,7 @@ function buildToolbarProps(input) {
12648
12187
  onAddShape: insertHandlers.handleAddShape,
12649
12188
  onAddTable: insertHandlers.handleAddTable,
12650
12189
  onAddChart: insertHandlers.handleAddChart,
12190
+ onOpenChartDialog: () => dialogs.setIsChartDialogOpen(true),
12651
12191
  onAddSmartArt: () => dialogs.setIsSmartArtDialogOpen(true),
12652
12192
  onAddEquation: () => {
12653
12193
  dialogs.setEditingEquationOmml(null);
@@ -14507,6 +14047,50 @@ var EDGE_HANDLES = [
14507
14047
  sizeClass: "w-2 h-5 max-md:w-3 max-md:h-8 rounded-sm"
14508
14048
  }
14509
14049
  ];
14050
+ var wholeDegrees = (deg) => Math.round((deg % 360 + 360) % 360) % 360;
14051
+ function startRotateKnobGesture(options) {
14052
+ const { button, elementId, rotation, onRotate } = options;
14053
+ const scope = button.closest("[data-pptx-viewport]") ?? document;
14054
+ const wrapper = scope.querySelector(elementIdSelector(elementId));
14055
+ if (!wrapper) {
14056
+ return;
14057
+ }
14058
+ const rect = wrapper.getBoundingClientRect();
14059
+ const base = options.nonRotationTransform ? ` ${options.nonRotationTransform}` : "";
14060
+ const paint = (deg) => {
14061
+ const transform = `rotate(${deg}deg)${base}`;
14062
+ wrapper.style.transform = transform;
14063
+ syncSelectionHandleOverlay(wrapper, elementId, { transform });
14064
+ };
14065
+ if (options.pointerId !== void 0) {
14066
+ button.setPointerCapture?.(options.pointerId);
14067
+ }
14068
+ const gesture = createGestureController({
14069
+ getScale: () => 1,
14070
+ getElementBox: () => ({
14071
+ x: rect.left,
14072
+ y: rect.top,
14073
+ width: rect.width,
14074
+ height: rect.height,
14075
+ rotation
14076
+ }),
14077
+ getSiblings: () => [],
14078
+ getStageOrigin: () => ({ left: 0, top: 0 }),
14079
+ onStart: () => void 0,
14080
+ onPreview: (transform) => paint(wholeDegrees(transform.rotation)),
14081
+ onEnd: (transform) => {
14082
+ const deg = transform ? wholeDegrees(transform.rotation) : rotation;
14083
+ if (deg !== rotation) {
14084
+ onRotate?.(elementId, deg);
14085
+ }
14086
+ }
14087
+ });
14088
+ gesture.begin("rotate", elementId, {
14089
+ clientX: options.pointer.clientX,
14090
+ clientY: options.pointer.clientY,
14091
+ shiftKey: false
14092
+ });
14093
+ }
14510
14094
  var appearance = { fill: "var(--color-primary)", borderColor: "#ffffff" };
14511
14095
  var cornerSize = "var(--pptx-react-corner-default, 12px)";
14512
14096
  var edgeLength = "var(--pptx-react-edge-length-default, 20px)";
@@ -14620,50 +14204,15 @@ function ResizeHandles({
14620
14204
  e.currentTarget.setPointerCapture?.(e.pointerId);
14621
14205
  onResizePointerDown(elementId, e, handle);
14622
14206
  };
14623
- const startRotate = (btn, pointer, pointerId) => {
14624
- const scope = btn.closest("[data-pptx-viewport]") ?? document;
14625
- const wrapper = scope.querySelector(elementIdSelector(elementId));
14626
- if (!wrapper) {
14627
- return;
14628
- }
14629
- const rect = wrapper.getBoundingClientRect();
14630
- const cx = rect.left + rect.width / 2;
14631
- const cy = rect.top + rect.height / 2;
14632
- const base = nonRotationTransform ? ` ${nonRotationTransform}` : "";
14633
- const startDeg = rotation ?? 0;
14634
- const dragRotation = createRotationDrag(
14635
- { x: cx, y: cy },
14636
- { x: pointer.clientX, y: pointer.clientY },
14637
- startDeg
14638
- );
14639
- let last = startDeg;
14640
- if (pointerId !== void 0) {
14641
- btn.setPointerCapture?.(pointerId);
14642
- }
14643
- const apply = (clientX, clientY, shift) => {
14644
- let deg = dragRotation({ x: clientX, y: clientY });
14645
- if (shift) {
14646
- deg = Math.round(deg / 15) * 15;
14647
- }
14648
- deg = Math.round((deg % 360 + 360) % 360);
14649
- last = deg;
14650
- const transform = `rotate(${deg}deg)${base}`;
14651
- wrapper.style.transform = transform;
14652
- syncSelectionHandleOverlay(wrapper, elementId, { transform });
14653
- };
14654
- const onPointerMove = (ev) => apply(ev.clientX, ev.clientY, ev.shiftKey);
14655
- const end = () => {
14656
- window.removeEventListener("pointermove", onPointerMove);
14657
- window.removeEventListener("pointerup", end);
14658
- window.removeEventListener("pointercancel", end);
14659
- if (last !== startDeg) {
14660
- onRotate?.(elementId, last);
14661
- }
14662
- };
14663
- window.addEventListener("pointermove", onPointerMove);
14664
- window.addEventListener("pointerup", end);
14665
- window.addEventListener("pointercancel", end);
14666
- };
14207
+ const startRotate = (button, pointer) => startRotateKnobGesture({
14208
+ button,
14209
+ elementId,
14210
+ pointer,
14211
+ pointerId: pointer.pointerId,
14212
+ rotation: rotation ?? 0,
14213
+ ...nonRotationTransform ? { nonRotationTransform } : {},
14214
+ ...onRotate ? { onRotate } : {}
14215
+ });
14667
14216
  return /* @__PURE__ */ jsxs(Fragment, { children: [
14668
14217
  [...CORNER_HANDLES, ...EDGE_HANDLES].map(({ handle, posClass, cursor }) => {
14669
14218
  const corner = handle.length === 2;
@@ -14734,7 +14283,7 @@ function ResizeHandles({
14734
14283
  },
14735
14284
  onPointerDown: (e) => {
14736
14285
  e.stopPropagation();
14737
- startRotate(e.currentTarget, e, e.pointerId);
14286
+ startRotate(e.currentTarget, e);
14738
14287
  },
14739
14288
  onMouseDown: (e) => {
14740
14289
  e.stopPropagation();
@@ -19144,4 +18693,4 @@ function useResizablePanels() {
19144
18693
  };
19145
18694
  }
19146
18695
 
19147
- 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 };
18696
+ 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 };