pptx-react-viewer 1.17.1 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/README.md +1 -1
  3. package/dist/{Model3DScene-YA4F63DT.js → Model3DScene-GVWQ6SOM.js} +3 -3
  4. package/dist/Model3DScene-GVWQ6SOM.js.br +0 -0
  5. package/dist/Model3DScene-GVWQ6SOM.js.gz +0 -0
  6. package/dist/{Model3DScene-QHDYQXL2.mjs → Model3DScene-JGVLIDLD.mjs} +2 -2
  7. package/dist/Model3DScene-JGVLIDLD.mjs.br +0 -0
  8. package/dist/Model3DScene-JGVLIDLD.mjs.gz +0 -0
  9. package/dist/{SurfaceChart3DScene-GUAS2NWQ.mjs → SurfaceChart3DScene-DBWSDSKI.mjs} +2 -2
  10. package/dist/SurfaceChart3DScene-DBWSDSKI.mjs.br +0 -0
  11. package/dist/SurfaceChart3DScene-DBWSDSKI.mjs.gz +0 -0
  12. package/dist/{SurfaceChart3DScene-TGNJHTJQ.js → SurfaceChart3DScene-ZOTOHOIE.js} +3 -3
  13. package/dist/SurfaceChart3DScene-ZOTOHOIE.js.br +0 -0
  14. package/dist/SurfaceChart3DScene-ZOTOHOIE.js.gz +0 -0
  15. package/dist/{chunk-WH7YXV3I.js → chunk-73ST7AC6.js} +2153 -1572
  16. package/dist/chunk-73ST7AC6.js.br +0 -0
  17. package/dist/chunk-73ST7AC6.js.gz +0 -0
  18. package/dist/{chunk-IZXN663G.js → chunk-AALZCSY4.js} +7 -0
  19. package/dist/chunk-AALZCSY4.js.br +0 -0
  20. package/dist/chunk-AALZCSY4.js.gz +0 -0
  21. package/dist/{chunk-MGLRKGCH.js → chunk-EXDUEXTO.js} +26101 -15360
  22. package/dist/chunk-EXDUEXTO.js.br +0 -0
  23. package/dist/chunk-EXDUEXTO.js.gz +0 -0
  24. package/dist/{chunk-JWS3RQKN.js → chunk-HBXPK572.js} +584 -144
  25. package/dist/chunk-HBXPK572.js.br +0 -0
  26. package/dist/chunk-HBXPK572.js.gz +0 -0
  27. package/dist/{chunk-G22EWPLH.mjs → chunk-HHTZQHKZ.mjs} +532 -119
  28. package/dist/chunk-HHTZQHKZ.mjs.br +0 -0
  29. package/dist/chunk-HHTZQHKZ.mjs.gz +0 -0
  30. package/dist/{chunk-SXCO5HHN.js → chunk-JDRC3PSY.js} +655 -491
  31. package/dist/chunk-JDRC3PSY.js.br +0 -0
  32. package/dist/chunk-JDRC3PSY.js.gz +0 -0
  33. package/dist/{chunk-PCQH2UBR.mjs → chunk-KMLIWCNL.mjs} +1345 -764
  34. package/dist/chunk-KMLIWCNL.mjs.br +0 -0
  35. package/dist/chunk-KMLIWCNL.mjs.gz +0 -0
  36. package/dist/{chunk-5CD34U2V.mjs → chunk-RIRK64DM.mjs} +7 -0
  37. package/dist/chunk-RIRK64DM.mjs.br +0 -0
  38. package/dist/chunk-RIRK64DM.mjs.gz +0 -0
  39. package/dist/{chunk-XRYREYXC.mjs → chunk-SBNTEQUA.mjs} +26062 -15360
  40. package/dist/chunk-SBNTEQUA.mjs.br +0 -0
  41. package/dist/chunk-SBNTEQUA.mjs.gz +0 -0
  42. package/dist/{chunk-HN7CPQUG.mjs → chunk-Z4MDCPAY.mjs} +223 -59
  43. package/dist/chunk-Z4MDCPAY.mjs.br +0 -0
  44. package/dist/chunk-Z4MDCPAY.mjs.gz +0 -0
  45. package/dist/{dist-2ZAH7U6U.js → dist-KRRXYUGI.js} +663 -507
  46. package/dist/dist-KRRXYUGI.js.br +0 -0
  47. package/dist/dist-KRRXYUGI.js.gz +0 -0
  48. package/dist/dist-N6PAPXK6.mjs +2 -0
  49. package/dist/dist-N6PAPXK6.mjs.br +0 -0
  50. package/dist/dist-N6PAPXK6.mjs.gz +0 -0
  51. package/dist/hooks-unstable.d.ts +484 -117
  52. package/dist/hooks-unstable.d.ts.map +1 -1
  53. package/dist/hooks-unstable.js +75 -75
  54. package/dist/hooks-unstable.js.br +0 -0
  55. package/dist/hooks-unstable.js.gz +0 -0
  56. package/dist/hooks-unstable.mjs +4 -4
  57. package/dist/hooks-unstable.mjs.br +0 -0
  58. package/dist/hooks-unstable.mjs.gz +0 -0
  59. package/dist/i18n.js +3 -3
  60. package/dist/i18n.js.br +0 -0
  61. package/dist/i18n.js.gz +0 -0
  62. package/dist/i18n.mjs +1 -1
  63. package/dist/i18n.mjs.br +0 -0
  64. package/dist/i18n.mjs.gz +0 -0
  65. package/dist/{index-XGt7MZpa.d.ts → index-DmHVR48M.d.ts} +25 -3
  66. package/dist/index-DmHVR48M.d.ts.map +1 -0
  67. package/dist/index.d.ts +412 -117
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +19 -19
  70. package/dist/index.js.br +0 -0
  71. package/dist/index.js.gz +0 -0
  72. package/dist/index.mjs +5 -5
  73. package/dist/index.mjs.br +0 -0
  74. package/dist/index.mjs.gz +0 -0
  75. package/dist/pptx-viewer.css +1 -1
  76. package/dist/pptx-viewer.css.br +0 -0
  77. package/dist/pptx-viewer.css.gz +0 -0
  78. package/dist/viewer/index.d.ts +438 -121
  79. package/dist/viewer/index.d.ts.map +1 -1
  80. package/dist/viewer/index.js +24 -20
  81. package/dist/viewer/index.js.br +0 -0
  82. package/dist/viewer/index.js.gz +0 -0
  83. package/dist/viewer/index.mjs +5 -5
  84. package/dist/viewer/index.mjs.br +0 -0
  85. package/dist/viewer/index.mjs.gz +0 -0
  86. package/package.json +1 -1
  87. package/dist/Model3DScene-QHDYQXL2.mjs.br +0 -0
  88. package/dist/Model3DScene-QHDYQXL2.mjs.gz +0 -0
  89. package/dist/Model3DScene-YA4F63DT.js.br +0 -0
  90. package/dist/Model3DScene-YA4F63DT.js.gz +0 -0
  91. package/dist/SurfaceChart3DScene-GUAS2NWQ.mjs.br +0 -0
  92. package/dist/SurfaceChart3DScene-GUAS2NWQ.mjs.gz +0 -0
  93. package/dist/SurfaceChart3DScene-TGNJHTJQ.js.br +0 -0
  94. package/dist/SurfaceChart3DScene-TGNJHTJQ.js.gz +0 -0
  95. package/dist/chunk-5CD34U2V.mjs.br +0 -0
  96. package/dist/chunk-5CD34U2V.mjs.gz +0 -0
  97. package/dist/chunk-G22EWPLH.mjs.br +0 -0
  98. package/dist/chunk-G22EWPLH.mjs.gz +0 -0
  99. package/dist/chunk-HN7CPQUG.mjs.br +0 -0
  100. package/dist/chunk-HN7CPQUG.mjs.gz +0 -0
  101. package/dist/chunk-IZXN663G.js.br +0 -0
  102. package/dist/chunk-IZXN663G.js.gz +0 -0
  103. package/dist/chunk-JWS3RQKN.js.br +0 -0
  104. package/dist/chunk-JWS3RQKN.js.gz +0 -0
  105. package/dist/chunk-MGLRKGCH.js.br +0 -0
  106. package/dist/chunk-MGLRKGCH.js.gz +0 -0
  107. package/dist/chunk-PCQH2UBR.mjs.br +0 -0
  108. package/dist/chunk-PCQH2UBR.mjs.gz +0 -0
  109. package/dist/chunk-SXCO5HHN.js.br +0 -0
  110. package/dist/chunk-SXCO5HHN.js.gz +0 -0
  111. package/dist/chunk-WH7YXV3I.js.br +0 -0
  112. package/dist/chunk-WH7YXV3I.js.gz +0 -0
  113. package/dist/chunk-XRYREYXC.mjs.br +0 -0
  114. package/dist/chunk-XRYREYXC.mjs.gz +0 -0
  115. package/dist/dist-2ZAH7U6U.js.br +0 -0
  116. package/dist/dist-2ZAH7U6U.js.gz +0 -0
  117. package/dist/dist-MXDC6YTS.mjs +0 -2
  118. package/dist/dist-MXDC6YTS.mjs.br +0 -0
  119. package/dist/dist-MXDC6YTS.mjs.gz +0 -0
  120. package/dist/index-XGt7MZpa.d.ts.map +0 -1
@@ -1,11 +1,11 @@
1
- import { DEFAULT_STROKE_COLOR, getConnectorPathGeometry, buildLineShadowCss, buildLineGlowFilter, renderConnectorMarker, MIN_ELEMENT_SIZE, getShapeVisualStyle, getTextStyleForElement, DEFAULT_TEXT_COLOR, renderVectorShape, isConnectorOrLineElement, build3DExtrusionData, getImageRenderStyle, cn, SHAPE_PRESETS, ANIMATION_PRESET_OPTIONS, useReducedMotion, useViewerState, useIsMobile, useResizablePanels, useDerivedSlideState, useZoomViewport, useEditorHistory, usePresentationSetup, useTouchGestures, useViewerDialogs, useEditorOperations, useViewerIntegration, useYjsDocumentSync, useFollowMode, useBroadcastFollower, resolvePalette, resolveStyle, layoutToCategory, wrapChrome, SLIDE_TRANSITION_OPTIONS, DEFAULT_FILL_COLOR, shouldRenderFallbackLabel, isImageTiled, getImageTilingStyle, renderTextSegments, getTextLayoutStyle, hasDagDuotoneEffect, renderDagDuotoneSvgFilter, renderTableElement, renderMediaElement, buildTextBody3DSceneStyle, getTextWarpStyle, getCropShapeClipPath, SLIDE_NAV_THUMBNAIL_WIDTH, useKeyboardInsets, useModalDismissDrag, SHORTCUT_REFERENCE_ITEMS, useCollaborativeState, styleShadow, styleStroke, colour, fitFontSize, smartArtNodeGroupProps, chevronPoints, SmartArtNodeText, truncate, renderStepDownProcess, renderAlternatingFlow, renderDescendingProcess, renderPictureAccentList, renderVerticalBlockList, renderGroupedList, renderPyramidList, renderHorizontalPictureList, renderAccentProcess, renderVerticalChevronList, parseTableElementData, ensureArrayValue, getTableCellBandStyle, extractCellText, extractTableCellStyle, cellStyleToCss, renderChartElement, getDuotoneColors, renderDuotoneSvgFilter, renderArtisticEffectSvgFilter, renderImageAlphaSvgFilter, buildReactChartViewModel, DEFAULT_TEXT_FONT_SIZE, DEFAULT_FONT_FAMILY, TOOLBAR_SECTIONS, useVirtualizedSlides, useSwipeNavigation, useSheetDismissDrag, detectTargetType, parseEmailUrl, parseSlideFromUrl, ACTION_VERB_MAP, buildTree, treeWidth, treeDepth, nodeOpacity, gearPath, resolveSmartArtDataPalette, EMU_PER_PX, HYPERLINK_COLOR, GRID_SIZE, startPreviewAnimation, stopPreviewAnimation } from './chunk-HN7CPQUG.mjs';
2
- import { normalizeHexColor, colorWithOpacity, normalizeStrokeDashType, getSvgStrokeDasharray, getCompoundLineOffsets, getCompoundLineWidths, svgLineCap, getElementTransform, ACTION_BUTTON_PRESETS, isEditableTextElement, getAriaRole, getAriaLabel, getAriaRoleDescription, getImageEffectsOpacity, getImageEffectsFilter, getElementTransformWithoutRotation, RULER_THICKNESS, RULER_FONT_SIZE, convertLatexToOmml, convertOmmlToMathMl, buildUserFontFaceStyles, openPptxFile, buildSmartArtA11y, buildCssGradientFromShapeStyle, getElementLabel, getTextCompensationTransform, getSlideBackgroundStyle, shouldUseSvgWarp, themeToCssVars, DEFAULT_INSERT_CHART_TYPE, hasCopyableFormat, resolveTransportForServerUrl, generateBroadcastRoomId, DEFAULT_BROADCAST_SERVER_URL, buildBroadcastViewerUrl, resolveDrawingShapeNodeId, getPendingSelectionRestore, restoreSegmentSelection, formatAxisValue, hasPressureVariation, getInkReplayStyles, INK_REPLAY_KEYFRAMES, resolveInkColor, resolveInkWidth, resolveInkOpacity, pressuresToWidths, getContentPartReplayStyles, resolveOleType, getOleTypeColor, getOleTypeLabel, getWarpPath, filterCommands, resolveTitleBarStatusKey, TITLE_BAR_CLASSES, TITLE_BAR_DEFAULT_FILE_KEY, safeOpenUrl, isPpactionUrl, parsePpactionUrl, formatVersionTimestamp, formatRelativeTime, PRESETS, CATEGORIES, convertOmmlToLatex, clampPercent, HANDOUT_OPTIONS, mobileElapsedSince, isFirstSlide, isLastSlide, formatMobileElapsed, mobileSlideCounter, NOTES_FONT_SIZE_DEFAULT, clampNotesFontSize, NOTES_FONT_SIZE_STEP, formatTime, formatElapsed, NOTES_FONT_SIZE_MIN, NOTES_FONT_SIZE_MAX, computeInlineEditorRect, findSmartArtNodeText, rebuildDrawingShapesIfCleared, shouldCommitSmartArtNodeText, buildDuotoneCacheKey, getDuotoneCachedResult, applyDuotone, setDuotoneCachedResult, buildCacheKey, DEFAULT_COLOR_CHANGE_TOLERANCE, getCachedResult, applyColorChange, setCachedResult, findChartPartTarget, dragValueForPart, withChartPointValue, dragAnchorViewY, withChartTitle, computeSmartArtLayout, buildSmartArt3DModel, extractPathPoints, generatePressureCircles, isBrowserOpenableMime, formatBytes, getOleBadgeLabel, openUrlInNewTab, groupIntoParagraphs, substituteFieldText, TAB_ROW_ACTION_CLASSES, INSERT_CHART_TYPES, getShapeAdjustmentHandleDescriptor, getSlideTransitionAnimations, SLIDE_TRANSITION_KEYFRAMES, formatSlideCounter, isUrlSafe, computeHandoutLayout, getPrintableArea, generateNoteLineCount, computeAllNotesPages, getNotesPrintableArea, notesSegmentsToSpans, getConnectionSites, generateTicks, getCommentMarkerPosition, buildThemeColorGrid, THEME_COLOR_LABELS, buildSmartArtPresetData, TABLE_STYLE_PRESETS, formatCommentTimestamp, DIRECTIONAL_PRESETS, computeMergeCellRight, computeMergeCellDown, computeSplitCell, DUOTONE_PRESETS, ARTISTIC_EFFECTS, LBL, SEL, FILL_MODE_OPTIONS, SECTION_HEADING, NUM, GRADIENT_TYPE_OPTIONS, PATTERN_OPTIONS } from './chunk-G22EWPLH.mjs';
3
- import { translationsEn } from './chunk-5CD34U2V.mjs';
4
- import { hasShapeProperties, hasTextProperties, SWITCHABLE_LAYOUT_TYPES, isInkElement, getLinkedTextBoxSegments, isImageLikeElement, getSubstituteFontFamily, setSmartArtNodeStyle, updateSmartArtNodeText, THEME_COLOR_SCHEME_KEYS, chartDataChangeType, chartDataUpdatePoint, chartDataAddCategory, chartDataRemoveCategory, chartDataAddSeries, chartDataRemoveSeries, setChartAxisLogScale, setChartAxisTitleStyle, setChartAxisGridlineStyle, setChartSeriesMarker, setChartSeriesChartType, setChartDataPointFill, setChartDataPointExplosion, setChartDataPointMarker, setChartDataPointLabel, getOleObjectTypeLabel, pptxActionToElementAction, hasNonTrivialOverride, COLOR_MAP_ALIAS_KEYS, DEFAULT_COLOR_MAP, addSmartArtNodeAsChild, removeSmartArtNode, elementActionToPptxAction, switchSmartArtLayout, addSmartArtNode, demoteSmartArtNode, promoteSmartArtNode, reorderSmartArtNode } from './chunk-XRYREYXC.mjs';
1
+ import { DEFAULT_STROKE_COLOR, getConnectorPathGeometry, buildLineShadowCss, buildLineGlowFilter, renderConnectorMarker, MIN_ELEMENT_SIZE, getShapeVisualStyle, getTextStyleForElement, DEFAULT_TEXT_COLOR, renderVectorShape, isConnectorOrLineElement, build3DExtrusionData, getImageRenderStyle, cn, SHAPE_PRESETS, ANIMATION_PRESET_OPTIONS, useReducedMotion, useViewerState, useIsMobile, useResizablePanels, useDerivedSlideState, useZoomViewport, useEditorHistory, usePresentationSetup, useTouchGestures, useViewerDialogs, useEditorOperations, useViewerIntegration, useYjsDocumentSync, useFollowMode, useBroadcastFollower, resolvePalette, resolveStyle, layoutToCategory, wrapChrome, SLIDE_TRANSITION_OPTIONS, DEFAULT_FILL_COLOR, shouldRenderFallbackLabel, isImageTiled, getImageTilingStyle, renderTextSegments, getTextLayoutStyle, hasDagDuotoneEffect, renderDagDuotoneSvgFilter, renderTableElement, renderMediaElement, buildTextBody3DSceneStyle, getTextWarpStyle, getCropShapeClipPath, SLIDE_NAV_THUMBNAIL_WIDTH, useKeyboardInsets, useModalDismissDrag, SHORTCUT_REFERENCE_ITEMS, useCollaborativeState, styleShadow, styleStroke, colour, fitFontSize, smartArtNodeGroupProps, chevronPoints, SmartArtNodeText, truncate, renderStepDownProcess, renderAlternatingFlow, renderDescendingProcess, renderPictureAccentList, renderVerticalBlockList, renderGroupedList, renderPyramidList, renderHorizontalPictureList, renderAccentProcess, renderVerticalChevronList, parseTableElementData, ensureArrayValue, getTableCellBandStyle, extractCellText, extractTableCellStyle, cellStyleToCss, renderChartElement, getDuotoneColors, renderDuotoneSvgFilter, renderArtisticEffectSvgFilter, renderImageAlphaSvgFilter, buildReactChartViewModel, DEFAULT_TEXT_FONT_SIZE, DEFAULT_FONT_FAMILY, TOOLBAR_SECTIONS, useVirtualizedSlides, useSwipeNavigation, useSheetDismissDrag, detectTargetType, parseEmailUrl, parseSlideFromUrl, ACTION_VERB_MAP, buildTree, treeWidth, treeDepth, nodeOpacity, gearPath, resolveSmartArtDataPalette, EMU_PER_PX, HYPERLINK_COLOR, GRID_SIZE, startPreviewAnimation, stopPreviewAnimation } from './chunk-Z4MDCPAY.mjs';
2
+ import { normalizeHexColor, colorWithOpacity, normalizeStrokeDashType, getSvgStrokeDasharray, getCompoundLineOffsets, getCompoundLineWidths, svgLineCap, getElementTransform, ACTION_BUTTON_PRESETS, isEditableTextElement, getAriaRole, getAriaLabel, getAriaRoleDescription, getImageEffectsOpacity, getImageEffectsFilter, getElementTransformWithoutRotation, RULER_THICKNESS, RULER_FONT_SIZE, convertLatexToOmml, convertOmmlToMathMl, buildUserFontFaceStyles, openPptxFile, buildSmartArtA11y, buildCssGradientFromShapeStyle, getElementLabel, getTextCompensationTransform, shouldUseSvgWarp, themeToCssVars, DEFAULT_INSERT_CHART_TYPE, hasCopyableFormat, buildJoinCollaborationConfig, buildCreateCollaborationConfig, generateBroadcastRoomId, DEFAULT_BROADCAST_SERVER_URL, buildBroadcastViewerUrl, resolveTransportForServerUrl, resolveDrawingShapeNodeId, getSlideBackgroundStyle, getPendingSelectionRestore, restoreSegmentSelection, formatAxisValue, hasPressureVariation, getInkReplayStyles, INK_REPLAY_KEYFRAMES, resolveInkColor, resolveInkWidth, resolveInkOpacity, pressuresToWidths, getContentPartReplayStyles, resolveOleType, getOleTypeColor, getOleTypeLabel, getWarpPath, filterCommands, resolveTitleBarStatusKey, TITLE_BAR_CLASSES, TITLE_BAR_DEFAULT_FILE_KEY, safeOpenUrl, isPpactionUrl, parsePpactionUrl, formatVersionTimestamp, formatRelativeTime, PRESETS, CATEGORIES, convertOmmlToLatex, clampPercent, HANDOUT_OPTIONS, activateModalFocus, buildCollaborationShareUrl, presentationInkPath, mobileElapsedSince, isFirstSlide, isLastSlide, formatMobileElapsed, mobileSlideCounter, formatElapsed, computeInlineEditorRect, findSmartArtNodeText, rebuildDrawingShapesIfCleared, shouldCommitSmartArtNodeText, buildDuotoneCacheKey, getDuotoneCachedResult, applyDuotone, setDuotoneCachedResult, buildCacheKey, DEFAULT_COLOR_CHANGE_TOLERANCE, getCachedResult, applyColorChange, setCachedResult, findChartPartTarget, dragValueForPart, withChartPointValue, dragAnchorViewY, withChartTitle, computeSmartArtLayout, buildSmartArt3DModel, extractPathPoints, generatePressureCircles, isBrowserOpenableMime, formatBytes, getOleBadgeLabel, openUrlInNewTab, groupIntoParagraphs, substituteFieldText, TAB_ROW_ACTION_CLASSES, INSERT_CHART_TYPES, getShapeAdjustmentHandleDescriptor, getSlideTransitionAnimations, SLIDE_TRANSITION_KEYFRAMES, formatSlideCounter, isUrlSafe, computeHandoutLayout, getPrintableArea, generateNoteLineCount, computeAllNotesPages, getNotesPrintableArea, notesSegmentsToSpans, erasePresentationInkAt, movePresenterPointer, appendPresentationInkPoint, NOTES_FONT_SIZE_DEFAULT, formatTime, NOTES_FONT_SIZE_MIN, clampNotesFontSize, NOTES_FONT_SIZE_STEP, NOTES_FONT_SIZE_MAX, getConnectionSites, generateTicks, getCommentMarkerPosition, buildThemeColorGrid, THEME_COLOR_LABELS, buildSmartArtPresetData, TABLE_STYLE_PRESETS, formatCommentTimestamp, DIRECTIONAL_PRESETS, computeMergeCellRight, computeMergeCellDown, computeSplitCell, DUOTONE_PRESETS, ARTISTIC_EFFECTS, LBL, SEL, FILL_MODE_OPTIONS, SECTION_HEADING, NUM, GRADIENT_TYPE_OPTIONS, PATTERN_OPTIONS } from './chunk-HHTZQHKZ.mjs';
3
+ import { translationsEn } from './chunk-RIRK64DM.mjs';
4
+ import { hasShapeProperties, hasTextProperties, SWITCHABLE_LAYOUT_TYPES, isInkElement, getLinkedTextBoxSegments, isImageLikeElement, getSubstituteFontFamily, themeColorSchemesEqual, setSmartArtNodeStyle, updateSmartArtNodeText, THEME_COLOR_SCHEME_KEYS, chartDataChangeType, chartDataUpdatePoint, chartDataAddCategory, chartDataRemoveCategory, chartDataAddSeries, chartDataRemoveSeries, setChartAxisLogScale, setChartAxisTitleStyle, setChartAxisGridlineStyle, setChartSeriesMarker, setChartSeriesChartType, setChartDataPointFill, setChartDataPointExplosion, setChartDataPointMarker, setChartDataPointLabel, getOleObjectTypeLabel, pptxActionToElementAction, applyThemeOverrideToSlide, COLOR_MAP_ALIAS_KEYS, DEFAULT_COLOR_MAP, addSmartArtNodeAsChild, removeSmartArtNode, elementActionToPptxAction, switchSmartArtLayout, addSmartArtNode, demoteSmartArtNode, promoteSmartArtNode, reorderSmartArtNode } from './chunk-SBNTEQUA.mjs';
5
5
  import React5, { createContext, useMemo, useState, useCallback, forwardRef, useEffect, useRef, useContext, Suspense, useLayoutEffect, useDeferredValue } from 'react';
6
6
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
7
  import { useTranslation } from 'react-i18next';
8
- import { LuRotateCw, LuMessageSquare, LuEyeOff, LuSettings, LuX, LuCast, LuSave, LuUndo, LuRedo, LuSearch, LuShieldCheck, LuPlus, LuPanelLeftClose, LuStickyNote, LuMonitor, LuColumns2, LuPresentation, LuMinus, LuLayers, LuSettings2, LuClock, LuDownload, LuTrash2, LuCheck, LuLock, LuEye, LuFileText, LuType, LuLoader, LuShieldAlert, LuSignature, LuInfo, LuTriangleAlert, LuPrinter, LuPenTool, LuWifi, LuWifiOff, LuUsers, LuCopy, LuChevronLeft, LuChevronRight, LuMonitorOff, LuPlay, LuPause, LuMenu, LuShare2, LuPanelLeft, LuPanelRight, LuFolderOpen, LuPackage, LuVideo, LuImage, LuClipboardPaste, LuScissors, LuPaintbrush, LuChevronDown, LuSquare, LuDatabase, LuAArrowUp, LuAArrowDown, LuRemoveFormatting, LuHighlighter, LuList, LuListOrdered, LuIndentDecrease, LuIndentIncrease, LuReplace, LuMousePointer2, LuShapes, LuPaintBucket, LuPenLine, LuSparkles, LuChevronUp, LuPalette, LuPencil, LuCaptions, LuSpellCheck, LuGitCompare, LuGlobe, LuPipette, LuCaseSensitive, LuTimer, LuEraser, LuGripVertical, LuUpload, LuBold, LuItalic, LuUnderline, LuStrikethrough, LuLink, LuGrid2X2, LuCopyPlus, LuClipboardCopy, LuLayoutGrid, LuWand, LuTextCursorInput, LuFile, LuEllipsis, LuRotateCcw, LuFolderPlus, LuAlignLeft, LuAlignCenter, LuAlignRight, LuAlignJustify, LuColumns3, LuMoveRight, LuSpline, LuAlignHorizontalSpaceAround, LuAlignVerticalSpaceAround, LuMove, LuRadio, LuLockOpen, LuArrowDown, LuArrowUp, LuArrowRight, LuArrowLeft, LuReply, LuBookmark } from 'react-icons/lu';
8
+ import { LuRotateCw, LuMessageSquare, LuEyeOff, LuSettings, LuX, LuCast, LuSave, LuUndo, LuRedo, LuSearch, LuShieldCheck, LuPlus, LuPanelLeftClose, LuStickyNote, LuMonitor, LuColumns2, LuPresentation, LuMinus, LuLayers, LuSettings2, LuClock, LuDownload, LuTrash2, LuCheck, LuLock, LuEye, LuFileText, LuType, LuLoader, LuShieldAlert, LuSignature, LuInfo, LuTriangleAlert, LuPrinter, LuPenTool, LuWifi, LuWifiOff, LuUsers, LuCopy, LuChevronLeft, LuChevronRight, LuPlay, LuPause, LuMenu, LuShare2, LuPanelLeft, LuPanelRight, LuFolderOpen, LuPackage, LuVideo, LuImage, LuClipboardPaste, LuScissors, LuPaintbrush, LuChevronDown, LuSquare, LuDatabase, LuAArrowUp, LuAArrowDown, LuRemoveFormatting, LuHighlighter, LuList, LuListOrdered, LuIndentDecrease, LuIndentIncrease, LuReplace, LuMousePointer2, LuShapes, LuPaintBucket, LuPenLine, LuSparkles, LuChevronUp, LuPalette, LuPencil, LuCaptions, LuSpellCheck, LuGitCompare, LuGlobe, LuPipette, LuCaseSensitive, LuTimer, LuEraser, LuGripVertical, LuUpload, LuBold, LuItalic, LuUnderline, LuStrikethrough, LuLink, LuGrid2X2, LuCopyPlus, LuCirclePlay, LuCirclePause, LuRotateCcw, LuZoomIn, LuZoomOut, LuScan, LuMonitorOff, LuClipboardCopy, LuLayoutGrid, LuWand, LuTextCursorInput, LuFile, LuEllipsis, LuFolderPlus, LuAlignLeft, LuAlignCenter, LuAlignRight, LuAlignJustify, LuColumns3, LuMoveRight, LuSpline, LuAlignHorizontalSpaceAround, LuAlignVerticalSpaceAround, LuMove, LuRadio, LuLockOpen, LuArrowDown, LuArrowUp, LuArrowRight, LuArrowLeft, LuReply, LuBookmark } from 'react-icons/lu';
9
9
  import DOMPurify from 'dompurify';
10
10
 
11
11
  var ViewerThemeContext = createContext(void 0);
@@ -466,12 +466,21 @@ function LoadingState({ className }) {
466
466
  return /* @__PURE__ */ jsx(
467
467
  "div",
468
468
  {
469
+ role: "status",
470
+ "aria-live": "polite",
471
+ "aria-atomic": "true",
469
472
  className: cn(
470
473
  "h-full w-full flex items-center justify-center text-muted-foreground",
471
474
  className
472
475
  ),
473
476
  children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
474
- /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-6 w-6 border-b-2 border-primary" }),
477
+ /* @__PURE__ */ jsx(
478
+ "div",
479
+ {
480
+ className: "animate-spin rounded-full h-6 w-6 border-b-2 border-primary",
481
+ "aria-hidden": "true"
482
+ }
483
+ ),
475
484
  /* @__PURE__ */ jsx("span", { children: t("pptx.viewer.loading") })
476
485
  ] })
477
486
  }
@@ -481,6 +490,8 @@ function ErrorState({ className, error }) {
481
490
  return /* @__PURE__ */ jsx(
482
491
  "div",
483
492
  {
493
+ role: "alert",
494
+ "aria-live": "assertive",
484
495
  className: cn(
485
496
  "h-full w-full flex items-center justify-center p-6 text-center text-red-400",
486
497
  className
@@ -884,6 +895,17 @@ function ResizeHandle({
884
895
  }
885
896
  );
886
897
  }
898
+
899
+ // src/viewer/utils/slide-background-style.ts
900
+ function getReactSlideBackgroundStyle(slide) {
901
+ const style = getSlideBackgroundStyle(slide);
902
+ return {
903
+ backgroundColor: style["background-color"],
904
+ backgroundImage: style["background-image"],
905
+ backgroundSize: style["background-size"],
906
+ backgroundRepeat: style["background-repeat"]
907
+ };
908
+ }
887
909
  function DrawingShapeRenderer({
888
910
  elementId,
889
911
  shapes,
@@ -2666,7 +2688,7 @@ function SlideThumbnailImpl({
2666
2688
  "div",
2667
2689
  {
2668
2690
  className: "relative w-full overflow-hidden rounded border border-border bg-white",
2669
- style: { height: previewHeight, ...getSlideBackgroundStyle(slide) },
2691
+ style: { height: previewHeight, ...getReactSlideBackgroundStyle(slide) },
2670
2692
  children: /* @__PURE__ */ jsxs(
2671
2693
  "div",
2672
2694
  {
@@ -4835,7 +4857,7 @@ function FailedToLoad() {
4835
4857
  var LazyModel3DScene = React5.lazy(
4836
4858
  async () => {
4837
4859
  try {
4838
- return await import('./Model3DScene-QHDYQXL2.mjs');
4860
+ return await import('./Model3DScene-JGVLIDLD.mjs');
4839
4861
  } catch {
4840
4862
  return { default: FailedToLoad };
4841
4863
  }
@@ -5810,7 +5832,7 @@ var ElementRenderer = React5.memo(
5810
5832
  const cur = effectiveIsInlineEditing ? "cursor-text" : effectiveCanInteract ? elementLocks?.noMove ? "cursor-default" : "cursor-move" : hasAction || isZoom ? "cursor-pointer" : "";
5811
5833
  const isPresentationPassive = !effectiveCanInteract;
5812
5834
  const isFullscreenMedia = el.type === "media" && Boolean(el.fullScreen) && isPresentationPassive && isMediaPlaying;
5813
- const ariaRole = getAriaRole(el);
5835
+ const ariaRole = isActionable ? "button" : getAriaRole(el);
5814
5836
  const ariaLabel = getAriaLabel(el);
5815
5837
  const ariaRoleDescription = getAriaRoleDescription(el);
5816
5838
  const isFocusable = effectiveCanInteract || isActionable;
@@ -5854,6 +5876,10 @@ var ElementRenderer = React5.memo(
5854
5876
  e.preventDefault();
5855
5877
  e.stopPropagation();
5856
5878
  onInlineEditCancel();
5879
+ } else if ((e.key === "Enter" || e.key === " ") && isActionable) {
5880
+ e.preventDefault();
5881
+ e.stopPropagation();
5882
+ e.currentTarget.click();
5857
5883
  }
5858
5884
  },
5859
5885
  onClick: (e) => {
@@ -6896,7 +6922,8 @@ var HANDOUT_SLIDES_PER_PAGE_OPTIONS = [1, 2, 3, 4, 6, 9];
6896
6922
  function HandoutMasterPanel({
6897
6923
  handoutMaster,
6898
6924
  slidesPerPage,
6899
- onSlidesPerPageChange
6925
+ onSlidesPerPageChange,
6926
+ onBackgroundChange
6900
6927
  }) {
6901
6928
  const { t } = useTranslation();
6902
6929
  if (!handoutMaster) {
@@ -6909,6 +6936,7 @@ function HandoutMasterPanel({
6909
6936
  "button",
6910
6937
  {
6911
6938
  type: "button",
6939
+ "aria-pressed": slidesPerPage === count,
6912
6940
  className: cn(
6913
6941
  "px-2 py-1.5 rounded text-[11px] font-medium transition-colors",
6914
6942
  slidesPerPage === count ? "bg-primary text-white" : "bg-accent/50 text-muted-foreground hover:bg-accent/70 hover:text-foreground"
@@ -6922,12 +6950,13 @@ function HandoutMasterPanel({
6922
6950
  /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-border/60 bg-muted/40 p-2", children: [
6923
6951
  /* @__PURE__ */ jsx("div", { className: "text-[10px] text-muted-foreground mb-1", children: t("pptx.master.handoutBackground") }),
6924
6952
  /* @__PURE__ */ jsx(
6925
- "div",
6953
+ "input",
6926
6954
  {
6955
+ type: "color",
6956
+ "aria-label": "Master background color",
6927
6957
  className: "w-full h-8 rounded border border-border",
6928
- style: {
6929
- backgroundColor: handoutMaster.backgroundColor ?? "#ffffff"
6930
- }
6958
+ value: handoutMaster.backgroundColor ?? "#ffffff",
6959
+ onChange: (event) => onBackgroundChange(event.target.value)
6931
6960
  }
6932
6961
  )
6933
6962
  ] }),
@@ -6947,7 +6976,10 @@ function HandoutMasterPanel({
6947
6976
  ] })
6948
6977
  ] });
6949
6978
  }
6950
- function NotesMasterPanel({ notesMaster }) {
6979
+ function NotesMasterPanel({
6980
+ notesMaster,
6981
+ onBackgroundChange
6982
+ }) {
6951
6983
  const { t } = useTranslation();
6952
6984
  const placeholderLabels = useMemo(() => {
6953
6985
  if (!notesMaster?.placeholders) {
@@ -6965,10 +6997,13 @@ function NotesMasterPanel({ notesMaster }) {
6965
6997
  /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-border/60 bg-muted/40 p-2", children: [
6966
6998
  /* @__PURE__ */ jsx("div", { className: "text-[10px] text-muted-foreground mb-1", children: t("pptx.master.notesMasterBackground") }),
6967
6999
  /* @__PURE__ */ jsx(
6968
- "div",
7000
+ "input",
6969
7001
  {
7002
+ type: "color",
7003
+ "aria-label": "Master background color",
6970
7004
  className: "w-full h-8 rounded border border-border",
6971
- style: { backgroundColor: notesMaster.backgroundColor ?? "#ffffff" }
7005
+ value: notesMaster.backgroundColor ?? "#ffffff",
7006
+ onChange: (event) => onBackgroundChange(event.target.value)
6972
7007
  }
6973
7008
  )
6974
7009
  ] }),
@@ -7115,7 +7150,9 @@ function MasterViewSidebar({
7115
7150
  onSelectLayout,
7116
7151
  onCollapse,
7117
7152
  onTabChange,
7118
- onHandoutSlidesPerPageChange
7153
+ onHandoutSlidesPerPageChange,
7154
+ onNotesMasterBackgroundChange,
7155
+ onHandoutMasterBackgroundChange
7119
7156
  }) {
7120
7157
  const scrollRef = useRef(null);
7121
7158
  const { t } = useTranslation();
@@ -7131,56 +7168,80 @@ function MasterViewSidebar({
7131
7168
  },
7132
7169
  [onSelectLayout]
7133
7170
  );
7134
- return /* @__PURE__ */ jsxs("aside", { className: "flex h-full flex-col border-r border-border/80 bg-background/70 backdrop-blur-sm w-56", children: [
7135
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-2", children: [
7136
- /* @__PURE__ */ jsx("span", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: masterViewTab === "slides" ? t("pptx.master.title") : masterViewTab === "notes" ? t("pptx.master.notesMasterTitle") : t("pptx.master.handoutMasterTitle") }),
7137
- /* @__PURE__ */ jsx(
7138
- "button",
7139
- {
7140
- type: "button",
7141
- className: "rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
7142
- title: t("pptx.master.collapseMasterPane"),
7143
- onClick: onCollapse,
7144
- children: /* @__PURE__ */ jsx(LuPanelLeftClose, { className: "h-3.5 w-3.5" })
7145
- }
7146
- )
7147
- ] }),
7148
- /* @__PURE__ */ jsx("div", { className: "flex border-b border-border/60 px-1", children: TABS.map((tab) => /* @__PURE__ */ jsx(
7149
- "button",
7150
- {
7151
- type: "button",
7152
- className: cn(
7153
- "flex-1 px-1 py-1.5 text-[10px] font-medium transition-colors border-b-2",
7154
- masterViewTab === tab.key ? "border-amber-500 text-amber-400" : "border-transparent text-muted-foreground hover:text-foreground"
7171
+ return /* @__PURE__ */ jsxs(
7172
+ "aside",
7173
+ {
7174
+ "aria-label": t("pptx.view.masterViews"),
7175
+ className: "flex h-full flex-col border-r border-border/80 bg-background/70 backdrop-blur-sm w-56",
7176
+ children: [
7177
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-2", children: [
7178
+ /* @__PURE__ */ jsx("span", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: masterViewTab === "slides" ? t("pptx.master.title") : masterViewTab === "notes" ? t("pptx.master.notesMasterTitle") : t("pptx.master.handoutMasterTitle") }),
7179
+ /* @__PURE__ */ jsx(
7180
+ "button",
7181
+ {
7182
+ type: "button",
7183
+ className: "rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
7184
+ title: t("pptx.master.collapseMasterPane"),
7185
+ onClick: onCollapse,
7186
+ children: /* @__PURE__ */ jsx(LuPanelLeftClose, { className: "h-3.5 w-3.5" })
7187
+ }
7188
+ )
7189
+ ] }),
7190
+ /* @__PURE__ */ jsx(
7191
+ "div",
7192
+ {
7193
+ role: "tablist",
7194
+ "aria-label": t("pptx.mode.masterView"),
7195
+ className: "flex border-b border-border/60 px-1",
7196
+ children: TABS.map((tab) => /* @__PURE__ */ jsx(
7197
+ "button",
7198
+ {
7199
+ type: "button",
7200
+ role: "tab",
7201
+ "aria-selected": masterViewTab === tab.key,
7202
+ className: cn(
7203
+ "flex-1 px-1 py-1.5 text-[10px] font-medium transition-colors border-b-2",
7204
+ masterViewTab === tab.key ? "border-amber-500 text-amber-400" : "border-transparent text-muted-foreground hover:text-foreground"
7205
+ ),
7206
+ onClick: () => onTabChange(tab.key),
7207
+ children: t(tab.labelKey)
7208
+ },
7209
+ tab.key
7210
+ ))
7211
+ }
7155
7212
  ),
7156
- onClick: () => onTabChange(tab.key),
7157
- children: t(tab.labelKey)
7158
- },
7159
- tab.key
7160
- )) }),
7161
- /* @__PURE__ */ jsxs("div", { ref: scrollRef, className: "flex-1 space-y-2 overflow-y-auto px-1.5 pb-2 pt-1", children: [
7162
- masterViewTab === "slides" && /* @__PURE__ */ jsx(
7163
- SlideMastersList,
7164
- {
7165
- slideMasters,
7166
- activeMasterIndex,
7167
- activeLayoutIndex,
7168
- canvasSize,
7169
- onSelectMaster: handleMasterClick,
7170
- onSelectLayout: handleLayoutClick
7171
- }
7172
- ),
7173
- masterViewTab === "notes" && /* @__PURE__ */ jsx(NotesMasterPanel, { notesMaster }),
7174
- masterViewTab === "handout" && /* @__PURE__ */ jsx(
7175
- HandoutMasterPanel,
7176
- {
7177
- handoutMaster,
7178
- slidesPerPage: handoutSlidesPerPage,
7179
- onSlidesPerPageChange: onHandoutSlidesPerPageChange
7180
- }
7181
- )
7182
- ] })
7183
- ] });
7213
+ /* @__PURE__ */ jsxs("div", { ref: scrollRef, className: "flex-1 space-y-2 overflow-y-auto px-1.5 pb-2 pt-1", children: [
7214
+ masterViewTab === "slides" && /* @__PURE__ */ jsx(
7215
+ SlideMastersList,
7216
+ {
7217
+ slideMasters,
7218
+ activeMasterIndex,
7219
+ activeLayoutIndex,
7220
+ canvasSize,
7221
+ onSelectMaster: handleMasterClick,
7222
+ onSelectLayout: handleLayoutClick
7223
+ }
7224
+ ),
7225
+ masterViewTab === "notes" && /* @__PURE__ */ jsx(
7226
+ NotesMasterPanel,
7227
+ {
7228
+ notesMaster,
7229
+ onBackgroundChange: onNotesMasterBackgroundChange
7230
+ }
7231
+ ),
7232
+ masterViewTab === "handout" && /* @__PURE__ */ jsx(
7233
+ HandoutMasterPanel,
7234
+ {
7235
+ handoutMaster,
7236
+ slidesPerPage: handoutSlidesPerPage,
7237
+ onSlidesPerPageChange: onHandoutSlidesPerPageChange,
7238
+ onBackgroundChange: onHandoutMasterBackgroundChange
7239
+ }
7240
+ )
7241
+ ] })
7242
+ ]
7243
+ }
7244
+ );
7184
7245
  }
7185
7246
  var DEFAULT_NOTES_WIDTH = 720;
7186
7247
  var DEFAULT_NOTES_HEIGHT = 960;
@@ -7200,6 +7261,12 @@ var DEFAULT_POSITIONS = {
7200
7261
  dt: { x: 0.6, y: 0, w: 0.4, h: 0.04 },
7201
7262
  sldNum: { x: 0.6, y: 0.96, w: 0.4, h: 0.04 }
7202
7263
  };
7264
+ function elementText(element) {
7265
+ if ("textSegments" in element && element.textSegments?.length) {
7266
+ return element.textSegments.map((segment) => segment.text).join("");
7267
+ }
7268
+ return "text" in element ? element.text ?? "" : "";
7269
+ }
7203
7270
  function NotesMasterCanvas({
7204
7271
  notesMaster,
7205
7272
  canvasSize,
@@ -7343,7 +7410,24 @@ function NotesMasterCanvas({
7343
7410
  },
7344
7411
  `${ph.type}-${ph.idx ?? "default"}`
7345
7412
  );
7346
- })
7413
+ }),
7414
+ notesMaster.elements?.map((element) => /* @__PURE__ */ jsx(
7415
+ "div",
7416
+ {
7417
+ "data-pptx-element": "true",
7418
+ "data-element-id": element.id,
7419
+ className: "absolute overflow-hidden",
7420
+ style: {
7421
+ left: element.x * scale,
7422
+ top: element.y * scale,
7423
+ width: element.width * scale,
7424
+ height: element.height * scale,
7425
+ zIndex: 2
7426
+ },
7427
+ children: elementText(element)
7428
+ },
7429
+ element.id
7430
+ ))
7347
7431
  ]
7348
7432
  }
7349
7433
  ) });
@@ -7351,6 +7435,12 @@ function NotesMasterCanvas({
7351
7435
  var PAGE_WIDTH = 720;
7352
7436
  var PAGE_HEIGHT = 960;
7353
7437
  var MARGIN = 0.06;
7438
+ function elementText2(element) {
7439
+ if ("textSegments" in element && element.textSegments?.length) {
7440
+ return element.textSegments.map((segment) => segment.text).join("");
7441
+ }
7442
+ return "text" in element ? element.text ?? "" : "";
7443
+ }
7354
7444
  function computeSlotLayout(slidesPerPage, slideAspect) {
7355
7445
  const mx = MARGIN;
7356
7446
  const my = MARGIN;
@@ -7502,6 +7592,23 @@ function HandoutMasterCanvas({
7502
7592
  i
7503
7593
  );
7504
7594
  }),
7595
+ handoutMaster.elements?.map((element) => /* @__PURE__ */ jsx(
7596
+ "div",
7597
+ {
7598
+ "data-pptx-element": "true",
7599
+ "data-element-id": element.id,
7600
+ className: "absolute overflow-hidden",
7601
+ style: {
7602
+ left: element.x * scale,
7603
+ top: element.y * scale,
7604
+ width: element.width * scale,
7605
+ height: element.height * scale,
7606
+ zIndex: 2
7607
+ },
7608
+ children: elementText2(element)
7609
+ },
7610
+ element.id
7611
+ )),
7505
7612
  /* @__PURE__ */ jsx(
7506
7613
  "div",
7507
7614
  {
@@ -9942,6 +10049,18 @@ function ComparePanel({
9942
10049
  )) })
9943
10050
  ] });
9944
10051
  }
10052
+ function useModalFocus(open, panelRef, onClose, initialFocusRef) {
10053
+ useEffect(() => {
10054
+ const panel = panelRef.current;
10055
+ if (!open || !panel) {
10056
+ return;
10057
+ }
10058
+ return activateModalFocus(panel, {
10059
+ initialFocus: initialFocusRef?.current,
10060
+ onEscape: onClose
10061
+ });
10062
+ }, [initialFocusRef, onClose, open, panelRef]);
10063
+ }
9945
10064
  function getPasswordStrength(password) {
9946
10065
  if (!password) {
9947
10066
  return 0;
@@ -9983,6 +10102,8 @@ function PasswordProtectionDialog({
9983
10102
  const [confirmPassword, setConfirmPassword] = useState("");
9984
10103
  const [showPassword, setShowPassword] = useState(false);
9985
10104
  const [error, setError] = useState("");
10105
+ const dialogRef = useRef(null);
10106
+ const passwordRef = useRef(null);
9986
10107
  const strength = useMemo(() => getPasswordStrength(password), [password]);
9987
10108
  const strengthLabel = useMemo(() => {
9988
10109
  const labels = [
@@ -10025,6 +10146,7 @@ function PasswordProtectionDialog({
10025
10146
  setError("");
10026
10147
  onClose();
10027
10148
  }, [onClose]);
10149
+ useModalFocus(isOpen, dialogRef, handleClose, passwordRef);
10028
10150
  if (!isOpen) {
10029
10151
  return null;
10030
10152
  }
@@ -10044,117 +10166,148 @@ function PasswordProtectionDialog({
10044
10166
  {
10045
10167
  style: { zIndex: 1201 },
10046
10168
  className: "fixed inset-0 flex items-center justify-center pointer-events-none",
10047
- children: /* @__PURE__ */ jsxs("div", { className: "pointer-events-auto w-[420px] rounded-xl border border-border bg-popover backdrop-blur-xl shadow-2xl max-md:fixed max-md:inset-x-0 max-md:bottom-0 max-md:top-auto max-md:w-full max-md:max-h-[88dvh] max-md:overflow-y-auto max-md:rounded-t-2xl max-md:rounded-b-none max-md:border-x-0 max-md:border-b-0 max-md:pb-[max(env(safe-area-inset-bottom),0px)]", children: [
10048
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 py-4 border-b border-border/60", children: [
10049
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
10050
- /* @__PURE__ */ jsx(LuLock, { className: "w-5 h-5 text-primary" }),
10051
- /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-foreground", children: t("pptx.security.protectPresentation") })
10052
- ] }),
10053
- /* @__PURE__ */ jsx(
10054
- "button",
10055
- {
10056
- type: "button",
10057
- onClick: handleClose,
10058
- className: "p-1 rounded hover:bg-accent transition-colors",
10059
- "aria-label": t("common.close"),
10060
- children: /* @__PURE__ */ jsx(LuX, { className: "w-4 h-4 text-muted-foreground" })
10061
- }
10062
- )
10063
- ] }),
10064
- /* @__PURE__ */ jsxs("div", { className: "px-5 py-4 space-y-4", children: [
10065
- isCurrentlyProtected && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-lg bg-green-900/30 border border-green-700/40 px-3 py-2", children: [
10066
- /* @__PURE__ */ jsx(LuShieldCheck, { className: "w-4 h-4 text-green-400 shrink-0" }),
10067
- /* @__PURE__ */ jsx("span", { className: "text-xs text-green-300", children: t("pptx.security.currentlyProtected") })
10068
- ] }),
10069
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("pptx.security.description") }),
10070
- /* @__PURE__ */ jsxs("div", { children: [
10071
- /* @__PURE__ */ jsx("label", { className: "block text-xs text-foreground mb-1", children: t("pptx.security.password") }),
10072
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
10169
+ children: /* @__PURE__ */ jsxs(
10170
+ "div",
10171
+ {
10172
+ ref: dialogRef,
10173
+ role: "dialog",
10174
+ "aria-modal": "true",
10175
+ "aria-labelledby": "pptx-password-title",
10176
+ tabIndex: -1,
10177
+ className: "pointer-events-auto w-[420px] overscroll-contain rounded-xl border border-border bg-popover backdrop-blur-xl shadow-2xl max-md:fixed max-md:inset-x-0 max-md:bottom-0 max-md:top-auto max-md:w-full max-md:max-h-[88dvh] max-md:overflow-y-auto max-md:rounded-t-2xl max-md:rounded-b-none max-md:border-x-0 max-md:border-b-0 max-md:pb-[max(env(safe-area-inset-bottom),0px)]",
10178
+ children: [
10179
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 py-4 border-b border-border/60", children: [
10180
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
10181
+ /* @__PURE__ */ jsx(LuLock, { className: "w-5 h-5 text-primary", "aria-hidden": "true" }),
10182
+ /* @__PURE__ */ jsx("h2", { id: "pptx-password-title", className: "text-sm font-semibold text-foreground", children: t("pptx.security.protectPresentation") })
10183
+ ] }),
10073
10184
  /* @__PURE__ */ jsx(
10074
- "input",
10185
+ "button",
10075
10186
  {
10076
- type: showPassword ? "text" : "password",
10077
- className: "w-full rounded-lg border border-border bg-muted px-3 py-2 text-sm text-foreground placeholder-muted-foreground focus:border-primary focus:ring-1 focus:ring-primary outline-none",
10078
- placeholder: t("pptx.security.passwordPlaceholder"),
10079
- value: password,
10080
- onChange: (e) => {
10081
- setPassword(e.target.value);
10082
- setError("");
10083
- }
10187
+ type: "button",
10188
+ onClick: handleClose,
10189
+ className: "p-1 rounded hover:bg-accent transition-colors",
10190
+ "aria-label": t("common.close"),
10191
+ children: /* @__PURE__ */ jsx(LuX, { className: "w-4 h-4 text-muted-foreground", "aria-hidden": "true" })
10084
10192
  }
10085
- ),
10086
- /* @__PURE__ */ jsx(
10193
+ )
10194
+ ] }),
10195
+ /* @__PURE__ */ jsxs("div", { className: "px-5 py-4 space-y-4", children: [
10196
+ isCurrentlyProtected && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-lg bg-green-900/30 border border-green-700/40 px-3 py-2", children: [
10197
+ /* @__PURE__ */ jsx(LuShieldCheck, { className: "w-4 h-4 text-green-400 shrink-0", "aria-hidden": "true" }),
10198
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-green-300", children: t("pptx.security.currentlyProtected") })
10199
+ ] }),
10200
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("pptx.security.description") }),
10201
+ /* @__PURE__ */ jsxs("div", { children: [
10202
+ /* @__PURE__ */ jsx("label", { htmlFor: "pptx-password", className: "block text-xs text-foreground mb-1", children: t("pptx.security.password") }),
10203
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
10204
+ /* @__PURE__ */ jsx(
10205
+ "input",
10206
+ {
10207
+ ref: passwordRef,
10208
+ id: "pptx-password",
10209
+ name: "presentation-password",
10210
+ autoComplete: "new-password",
10211
+ "aria-invalid": Boolean(error),
10212
+ "aria-describedby": error ? "pptx-password-error" : "pptx-password-strength",
10213
+ type: showPassword ? "text" : "password",
10214
+ className: "w-full rounded-lg border border-border bg-muted px-3 py-2 text-sm text-foreground placeholder-muted-foreground focus:border-primary focus:ring-1 focus:ring-primary outline-none",
10215
+ placeholder: t("pptx.security.passwordPlaceholder"),
10216
+ value: password,
10217
+ onChange: (e) => {
10218
+ setPassword(e.target.value);
10219
+ setError("");
10220
+ }
10221
+ }
10222
+ ),
10223
+ /* @__PURE__ */ jsx(
10224
+ "button",
10225
+ {
10226
+ type: "button",
10227
+ onClick: () => setShowPassword(!showPassword),
10228
+ className: "absolute right-2 top-1/2 -translate-y-1/2 p-1 text-muted-foreground hover:text-foreground",
10229
+ "aria-label": showPassword ? t("pptx.security.hidePassword") : t("pptx.security.showPassword"),
10230
+ children: showPassword ? /* @__PURE__ */ jsx(LuEyeOff, { className: "w-4 h-4", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(LuEye, { className: "w-4 h-4", "aria-hidden": "true" })
10231
+ }
10232
+ )
10233
+ ] })
10234
+ ] }),
10235
+ password && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
10236
+ /* @__PURE__ */ jsx("div", { className: "flex gap-1", children: Array.from({ length: 5 }).map((_, i) => /* @__PURE__ */ jsx(
10237
+ "div",
10238
+ {
10239
+ className: `h-1 flex-1 rounded-full transition-colors ${i <= strength ? STRENGTH_COLORS[strength] : "bg-accent"}`
10240
+ },
10241
+ `str-${i}`
10242
+ )) }),
10243
+ /* @__PURE__ */ jsx(
10244
+ "p",
10245
+ {
10246
+ id: "pptx-password-strength",
10247
+ className: "text-[11px] text-muted-foreground",
10248
+ role: "status",
10249
+ "aria-live": "polite",
10250
+ children: strengthLabel
10251
+ }
10252
+ )
10253
+ ] }),
10254
+ /* @__PURE__ */ jsxs("div", { children: [
10255
+ /* @__PURE__ */ jsx("label", { htmlFor: "pptx-password-confirm", className: "block text-xs text-foreground mb-1", children: t("pptx.security.confirmPassword") }),
10256
+ /* @__PURE__ */ jsx(
10257
+ "input",
10258
+ {
10259
+ id: "pptx-password-confirm",
10260
+ name: "presentation-password-confirmation",
10261
+ autoComplete: "new-password",
10262
+ "aria-invalid": Boolean(error),
10263
+ "aria-describedby": error ? "pptx-password-error" : void 0,
10264
+ type: showPassword ? "text" : "password",
10265
+ className: "w-full rounded-lg border border-border bg-muted px-3 py-2 text-sm text-foreground placeholder-muted-foreground focus:border-primary focus:ring-1 focus:ring-primary outline-none",
10266
+ placeholder: t("pptx.security.confirmPasswordPlaceholder"),
10267
+ value: confirmPassword,
10268
+ onChange: (e) => {
10269
+ setConfirmPassword(e.target.value);
10270
+ setError("");
10271
+ }
10272
+ }
10273
+ )
10274
+ ] }),
10275
+ error && /* @__PURE__ */ jsx("p", { id: "pptx-password-error", className: "text-xs text-red-400", role: "alert", children: error })
10276
+ ] }),
10277
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 py-3 border-t border-border/60", children: [
10278
+ /* @__PURE__ */ jsx("div", { children: isCurrentlyProtected && /* @__PURE__ */ jsx(
10087
10279
  "button",
10088
10280
  {
10089
10281
  type: "button",
10090
- onClick: () => setShowPassword(!showPassword),
10091
- className: "absolute right-2 top-1/2 -translate-y-1/2 p-1 text-muted-foreground hover:text-foreground",
10092
- "aria-label": showPassword ? t("pptx.security.hidePassword") : t("pptx.security.showPassword"),
10093
- children: showPassword ? /* @__PURE__ */ jsx(LuEyeOff, { className: "w-4 h-4" }) : /* @__PURE__ */ jsx(LuEye, { className: "w-4 h-4" })
10282
+ onClick: handleRemove,
10283
+ className: "text-xs text-red-400 hover:text-red-300 transition-colors",
10284
+ children: t("pptx.security.removePassword")
10094
10285
  }
10095
- )
10286
+ ) }),
10287
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
10288
+ /* @__PURE__ */ jsx(
10289
+ "button",
10290
+ {
10291
+ type: "button",
10292
+ onClick: handleClose,
10293
+ className: "px-3 py-1.5 text-xs rounded-lg border border-border text-foreground hover:bg-muted transition-colors",
10294
+ children: t("common.cancel")
10295
+ }
10296
+ ),
10297
+ /* @__PURE__ */ jsx(
10298
+ "button",
10299
+ {
10300
+ type: "button",
10301
+ onClick: handleSubmit,
10302
+ className: "px-3 py-1.5 text-xs rounded-lg bg-primary text-white hover:bg-primary/80 transition-colors",
10303
+ children: isCurrentlyProtected ? t("pptx.security.updatePassword") : t("pptx.security.setPassword")
10304
+ }
10305
+ )
10306
+ ] })
10096
10307
  ] })
10097
- ] }),
10098
- password && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
10099
- /* @__PURE__ */ jsx("div", { className: "flex gap-1", children: Array.from({ length: 5 }).map((_, i) => /* @__PURE__ */ jsx(
10100
- "div",
10101
- {
10102
- className: `h-1 flex-1 rounded-full transition-colors ${i <= strength ? STRENGTH_COLORS[strength] : "bg-accent"}`
10103
- },
10104
- `str-${i}`
10105
- )) }),
10106
- /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: strengthLabel })
10107
- ] }),
10108
- /* @__PURE__ */ jsxs("div", { children: [
10109
- /* @__PURE__ */ jsx("label", { className: "block text-xs text-foreground mb-1", children: t("pptx.security.confirmPassword") }),
10110
- /* @__PURE__ */ jsx(
10111
- "input",
10112
- {
10113
- type: showPassword ? "text" : "password",
10114
- className: "w-full rounded-lg border border-border bg-muted px-3 py-2 text-sm text-foreground placeholder-muted-foreground focus:border-primary focus:ring-1 focus:ring-primary outline-none",
10115
- placeholder: t("pptx.security.confirmPasswordPlaceholder"),
10116
- value: confirmPassword,
10117
- onChange: (e) => {
10118
- setConfirmPassword(e.target.value);
10119
- setError("");
10120
- }
10121
- }
10122
- )
10123
- ] }),
10124
- error && /* @__PURE__ */ jsx("p", { className: "text-xs text-red-400", children: error })
10125
- ] }),
10126
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 py-3 border-t border-border/60", children: [
10127
- /* @__PURE__ */ jsx("div", { children: isCurrentlyProtected && /* @__PURE__ */ jsx(
10128
- "button",
10129
- {
10130
- type: "button",
10131
- onClick: handleRemove,
10132
- className: "text-xs text-red-400 hover:text-red-300 transition-colors",
10133
- children: t("pptx.security.removePassword")
10134
- }
10135
- ) }),
10136
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
10137
- /* @__PURE__ */ jsx(
10138
- "button",
10139
- {
10140
- type: "button",
10141
- onClick: handleClose,
10142
- className: "px-3 py-1.5 text-xs rounded-lg border border-border text-foreground hover:bg-muted transition-colors",
10143
- children: t("common.cancel")
10144
- }
10145
- ),
10146
- /* @__PURE__ */ jsx(
10147
- "button",
10148
- {
10149
- type: "button",
10150
- onClick: handleSubmit,
10151
- className: "px-3 py-1.5 text-xs rounded-lg bg-primary text-white hover:bg-primary/80 transition-colors",
10152
- children: isCurrentlyProtected ? t("pptx.security.updatePassword") : t("pptx.security.setPassword")
10153
- }
10154
- )
10155
- ] })
10156
- ] })
10157
- ] })
10308
+ ]
10309
+ }
10310
+ )
10158
10311
  }
10159
10312
  )
10160
10313
  ] });
@@ -10986,18 +11139,225 @@ function EncryptedFileDialog({
10986
11139
  )
10987
11140
  ] });
10988
11141
  }
10989
- function renderNotesSegments(segments) {
10990
- return notesSegmentsToSpans(segments).map((span) => {
10991
- if (span.kind === "break") {
10992
- return React5.createElement("br", { key: span.key });
10993
- }
10994
- return React5.createElement(
11142
+ function PresentationAudienceEffects({ snapshot }) {
11143
+ const strokes = snapshot.inkStrokes?.filter((stroke) => stroke.slideIndex === snapshot.slideIndex) ?? [];
11144
+ return /* @__PURE__ */ jsxs("div", { className: "pointer-events-none absolute inset-0 z-[75] overflow-hidden", children: [
11145
+ snapshot.blackout !== "none" && /* @__PURE__ */ jsx(
11146
+ "div",
11147
+ {
11148
+ className: `absolute inset-0 ${snapshot.blackout === "black" ? "bg-black" : "bg-white"}`
11149
+ }
11150
+ ),
11151
+ /* @__PURE__ */ jsx(
11152
+ "svg",
11153
+ {
11154
+ className: "absolute inset-0 h-full w-full",
11155
+ viewBox: "0 0 100 100",
11156
+ preserveAspectRatio: "none",
11157
+ "aria-hidden": "true",
11158
+ children: strokes.map((stroke) => /* @__PURE__ */ jsx(
11159
+ "path",
11160
+ {
11161
+ d: presentationInkPath(stroke.points),
11162
+ fill: "none",
11163
+ stroke: stroke.color,
11164
+ strokeWidth: stroke.width / 10,
11165
+ strokeLinecap: "round",
11166
+ strokeLinejoin: "round",
11167
+ opacity: stroke.tool === "highlighter" ? 0.42 : 1
11168
+ },
11169
+ stroke.id
11170
+ ))
11171
+ }
11172
+ ),
11173
+ snapshot.pointer?.tool === "laser" && /* @__PURE__ */ jsx(
10995
11174
  "span",
10996
- { key: span.key, style: span.style },
10997
- span.text
10998
- );
10999
- });
11000
- }
11175
+ {
11176
+ className: "absolute h-5 w-5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-red-500 shadow-[0_0_20px_8px_rgba(239,68,68,.55)]",
11177
+ style: { left: `${snapshot.pointer.x * 100}%`, top: `${snapshot.pointer.y * 100}%` }
11178
+ }
11179
+ ),
11180
+ snapshot.subtitlesVisible && snapshot.caption && /* @__PURE__ */ jsx("div", { className: "absolute inset-x-[10%] bottom-8 rounded-lg bg-black/80 px-6 py-3 text-center text-xl font-medium text-white", children: snapshot.caption })
11181
+ ] });
11182
+ }
11183
+ function PresentationSubtitleBar({
11184
+ visible,
11185
+ onCaptionChange
11186
+ }) {
11187
+ const { t } = useTranslation();
11188
+ const [captionText, setCaptionText] = useState("");
11189
+ const [supportState, setSupportState] = useState(
11190
+ "unknown"
11191
+ );
11192
+ const recognitionRef = useRef(null);
11193
+ const shouldRunRef = useRef(false);
11194
+ useEffect(() => {
11195
+ if (!visible) {
11196
+ shouldRunRef.current = false;
11197
+ recognitionRef.current?.stop();
11198
+ recognitionRef.current = null;
11199
+ setCaptionText("");
11200
+ return;
11201
+ }
11202
+ shouldRunRef.current = true;
11203
+ const speechWindow = window;
11204
+ const RecognitionCtor = speechWindow.SpeechRecognition ?? speechWindow.webkitSpeechRecognition;
11205
+ if (!RecognitionCtor) {
11206
+ setSupportState("unsupported");
11207
+ return;
11208
+ }
11209
+ setSupportState("supported");
11210
+ const recognition = new RecognitionCtor();
11211
+ recognition.continuous = true;
11212
+ recognition.interimResults = true;
11213
+ recognition.lang = navigator.language || "en-US";
11214
+ recognition.onresult = (event) => {
11215
+ let finalText = "";
11216
+ let interimText = "";
11217
+ for (let index = event.resultIndex; index < event.results.length; index += 1) {
11218
+ const result = event.results[index];
11219
+ const fragment = result?.[0]?.transcript ?? "";
11220
+ if (result?.isFinal) {
11221
+ finalText += fragment;
11222
+ } else {
11223
+ interimText += fragment;
11224
+ }
11225
+ }
11226
+ const merged = `${finalText} ${interimText}`.trim();
11227
+ if (merged.length > 0) {
11228
+ setCaptionText(merged);
11229
+ onCaptionChange?.(merged);
11230
+ }
11231
+ };
11232
+ recognition.onerror = () => {
11233
+ };
11234
+ recognition.onend = () => {
11235
+ if (!shouldRunRef.current) {
11236
+ return;
11237
+ }
11238
+ try {
11239
+ recognition.start();
11240
+ } catch {
11241
+ }
11242
+ };
11243
+ recognitionRef.current = recognition;
11244
+ try {
11245
+ recognition.start();
11246
+ } catch {
11247
+ setSupportState("unsupported");
11248
+ }
11249
+ return () => {
11250
+ shouldRunRef.current = false;
11251
+ recognition.stop();
11252
+ recognitionRef.current = null;
11253
+ };
11254
+ }, [visible, onCaptionChange]);
11255
+ if (!visible) {
11256
+ return null;
11257
+ }
11258
+ const renderedText = supportState === "unsupported" ? t("pptx.subtitles.notSupported") : captionText.length > 0 ? captionText : t("pptx.subtitles.listening");
11259
+ return /* @__PURE__ */ jsx("div", { className: "absolute bottom-14 left-1/2 -translate-x-1/2 z-[70] max-w-[80%] min-w-[300px]", children: /* @__PURE__ */ jsx("div", { className: "px-6 py-3 rounded-lg bg-black/75 backdrop-blur-sm border border-white/10", children: /* @__PURE__ */ jsx("p", { className: "text-center text-[15px] text-white/70 italic", children: renderedText }) }) });
11260
+ }
11261
+ function renderNotesSegments(segments) {
11262
+ return notesSegmentsToSpans(segments).map((span) => {
11263
+ if (span.kind === "break") {
11264
+ return React5.createElement("br", { key: span.key });
11265
+ }
11266
+ return React5.createElement(
11267
+ "span",
11268
+ { key: span.key, style: span.style },
11269
+ span.text
11270
+ );
11271
+ });
11272
+ }
11273
+ var toolClass = (active) => `inline-flex h-9 min-w-9 items-center justify-center gap-2 rounded-md px-2 text-xs transition-colors ${active ? "bg-sky-500 text-slate-950" : "bg-white/7 text-slate-200 hover:bg-white/14"}`;
11274
+ function PresenterConsoleToolbar(props) {
11275
+ const pointerTool = props.snapshot.pointer?.tool ?? "none";
11276
+ const zoom = props.snapshot.zoom?.scale ?? 1;
11277
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1 border-b border-white/10 bg-slate-950 px-3 py-2", children: [
11278
+ /* @__PURE__ */ jsx(
11279
+ "button",
11280
+ {
11281
+ className: toolClass(false),
11282
+ onClick: props.onToggleTimer,
11283
+ title: "Pause or resume timer",
11284
+ children: props.snapshot.paused ? /* @__PURE__ */ jsx(LuCirclePlay, {}) : /* @__PURE__ */ jsx(LuCirclePause, {})
11285
+ }
11286
+ ),
11287
+ /* @__PURE__ */ jsx("button", { className: toolClass(false), onClick: props.onResetTimer, title: "Reset timer", children: /* @__PURE__ */ jsx(LuRotateCcw, {}) }),
11288
+ /* @__PURE__ */ jsx("span", { className: "mx-1 h-6 w-px bg-white/15" }),
11289
+ /* @__PURE__ */ jsxs("button", { className: toolClass(false), onClick: props.onShowSlides, title: "See all slides", children: [
11290
+ /* @__PURE__ */ jsx(LuGrid2X2, {}),
11291
+ " Slides"
11292
+ ] }),
11293
+ /* @__PURE__ */ jsx("button", { className: toolClass(zoom > 1), onClick: () => props.onStepZoom(1), title: "Zoom in", children: /* @__PURE__ */ jsx(LuZoomIn, {}) }),
11294
+ /* @__PURE__ */ jsx("button", { className: toolClass(false), onClick: () => props.onStepZoom(-1), title: "Zoom out", children: /* @__PURE__ */ jsx(LuZoomOut, {}) }),
11295
+ /* @__PURE__ */ jsx("button", { className: toolClass(false), onClick: props.onResetZoom, title: "Reset zoom", children: /* @__PURE__ */ jsx(LuScan, {}) }),
11296
+ /* @__PURE__ */ jsx("span", { className: "mx-1 h-6 w-px bg-white/15" }),
11297
+ ["laser", "pen", "highlighter", "eraser"].map((tool) => /* @__PURE__ */ jsx(
11298
+ "button",
11299
+ {
11300
+ className: toolClass(pointerTool === tool),
11301
+ onClick: () => props.onPointerTool(pointerTool === tool ? "none" : tool),
11302
+ title: tool[0].toUpperCase() + tool.slice(1),
11303
+ children: tool === "laser" ? /* @__PURE__ */ jsx(LuMousePointer2, {}) : tool === "pen" ? /* @__PURE__ */ jsx(LuPenTool, {}) : tool === "highlighter" ? /* @__PURE__ */ jsx(LuHighlighter, {}) : /* @__PURE__ */ jsx(LuEraser, {})
11304
+ },
11305
+ tool
11306
+ )),
11307
+ /* @__PURE__ */ jsx("span", { className: "mx-1 h-6 w-px bg-white/15" }),
11308
+ /* @__PURE__ */ jsx(
11309
+ "button",
11310
+ {
11311
+ className: toolClass(props.snapshot.blackout === "black"),
11312
+ onClick: () => props.onBlackout(props.snapshot.blackout === "black" ? "none" : "black"),
11313
+ title: "Black screen",
11314
+ children: "B"
11315
+ }
11316
+ ),
11317
+ /* @__PURE__ */ jsx(
11318
+ "button",
11319
+ {
11320
+ className: toolClass(props.snapshot.blackout === "white"),
11321
+ onClick: () => props.onBlackout(props.snapshot.blackout === "white" ? "none" : "white"),
11322
+ title: "White screen",
11323
+ children: "W"
11324
+ }
11325
+ ),
11326
+ /* @__PURE__ */ jsx(
11327
+ "button",
11328
+ {
11329
+ className: toolClass(Boolean(props.snapshot.subtitlesVisible)),
11330
+ onClick: props.onToggleSubtitles,
11331
+ title: "Toggle subtitles",
11332
+ children: /* @__PURE__ */ jsx(LuCaptions, {})
11333
+ }
11334
+ ),
11335
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
11336
+ /* @__PURE__ */ jsx(
11337
+ "button",
11338
+ {
11339
+ className: toolClass(props.audienceOpen),
11340
+ onClick: props.onToggleAudience,
11341
+ title: "Audience display",
11342
+ children: props.audienceOpen ? /* @__PURE__ */ jsx(LuMonitorOff, {}) : /* @__PURE__ */ jsx(LuMonitor, {})
11343
+ }
11344
+ ),
11345
+ /* @__PURE__ */ jsx(
11346
+ "button",
11347
+ {
11348
+ className: toolClass(false),
11349
+ onClick: props.onSwapDisplays,
11350
+ disabled: !props.audienceOpen,
11351
+ title: "Swap displays",
11352
+ children: "Swap"
11353
+ }
11354
+ ),
11355
+ /* @__PURE__ */ jsxs("button", { className: toolClass(false), onClick: props.onExit, title: "End slide show", children: [
11356
+ /* @__PURE__ */ jsx(LuX, {}),
11357
+ " End"
11358
+ ] })
11359
+ ] });
11360
+ }
11001
11361
  function ScaledSlidePreviewImpl({
11002
11362
  slide,
11003
11363
  templateElements,
@@ -11288,6 +11648,250 @@ function arePropsEqual4(prev, next) {
11288
11648
  return true;
11289
11649
  }
11290
11650
  var ScaledSlidePreview = React5.memo(ScaledSlidePreviewImpl, arePropsEqual4);
11651
+ function PresenterNotesRail({
11652
+ slides,
11653
+ current,
11654
+ canvasSize,
11655
+ templateElements,
11656
+ now,
11657
+ elapsed,
11658
+ onMove,
11659
+ onUpdateNotes
11660
+ }) {
11661
+ const { t } = useTranslation();
11662
+ const slide = slides[current];
11663
+ const nextSlide = slides.slice(current + 1).find((candidate) => !candidate.hidden);
11664
+ const notesText = slide?.notes ?? "";
11665
+ const notesSegments = slide?.notesSegments;
11666
+ const [notesDraft, setNotesDraft] = useState(notesText);
11667
+ const [fontSize, setFontSize] = useState(NOTES_FONT_SIZE_DEFAULT);
11668
+ useEffect(() => setNotesDraft(notesText), [current, notesText]);
11669
+ const notes = useMemo(
11670
+ () => notesSegments?.length ? renderNotesSegments(notesSegments) : notesText.trim() ? notesText : /* @__PURE__ */ jsx("span", { className: "italic text-muted-foreground", children: t("pptx.presenter.noNotes") }),
11671
+ [notesSegments, notesText, t]
11672
+ );
11673
+ return /* @__PURE__ */ jsxs("aside", { className: "flex flex-[3] min-w-[260px] max-w-[440px] flex-col border-l border-border bg-background", children: [
11674
+ /* @__PURE__ */ jsxs("header", { className: "flex items-center justify-between border-b border-border/60 px-4 py-3", children: [
11675
+ /* @__PURE__ */ jsxs("div", { children: [
11676
+ /* @__PURE__ */ jsx("div", { className: "text-[10px] uppercase tracking-wider text-muted-foreground", children: t("pptx.presenter.currentTime") }),
11677
+ /* @__PURE__ */ jsx("div", { className: "font-mono text-lg tabular-nums", children: formatTime(new Date(now)) })
11678
+ ] }),
11679
+ /* @__PURE__ */ jsxs("div", { className: "text-right", children: [
11680
+ /* @__PURE__ */ jsx("div", { className: "text-[10px] uppercase tracking-wider text-muted-foreground", children: t("pptx.presenter.elapsed") }),
11681
+ /* @__PURE__ */ jsx("div", { className: "font-mono text-lg tabular-nums text-primary", children: formatElapsed(elapsed) })
11682
+ ] })
11683
+ ] }),
11684
+ /* @__PURE__ */ jsxs("nav", { className: "flex items-center justify-between border-b border-border/60 px-4 py-2", children: [
11685
+ /* @__PURE__ */ jsxs(
11686
+ "button",
11687
+ {
11688
+ type: "button",
11689
+ onClick: () => onMove(-1),
11690
+ disabled: current === 0,
11691
+ className: "inline-flex items-center gap-1 rounded bg-muted px-3 py-1.5 text-xs disabled:opacity-40",
11692
+ children: [
11693
+ /* @__PURE__ */ jsx(LuChevronLeft, {}),
11694
+ " ",
11695
+ t("pptx.presenter.prev")
11696
+ ]
11697
+ }
11698
+ ),
11699
+ /* @__PURE__ */ jsxs("span", { className: "font-mono text-sm tabular-nums", children: [
11700
+ current + 1,
11701
+ " / ",
11702
+ slides.length
11703
+ ] }),
11704
+ /* @__PURE__ */ jsxs(
11705
+ "button",
11706
+ {
11707
+ type: "button",
11708
+ onClick: () => onMove(1),
11709
+ disabled: current >= slides.length - 1,
11710
+ className: "inline-flex items-center gap-1 rounded bg-muted px-3 py-1.5 text-xs disabled:opacity-40",
11711
+ children: [
11712
+ t("pptx.presenter.next"),
11713
+ " ",
11714
+ /* @__PURE__ */ jsx(LuChevronRight, {})
11715
+ ]
11716
+ }
11717
+ )
11718
+ ] }),
11719
+ /* @__PURE__ */ jsxs("section", { className: "border-b border-border/60 px-4 py-3", children: [
11720
+ /* @__PURE__ */ jsx("div", { className: "mb-2 text-[10px] uppercase tracking-wider text-muted-foreground", children: t("pptx.presenter.nextSlidePreview") }),
11721
+ nextSlide ? /* @__PURE__ */ jsx(
11722
+ ScaledSlidePreview,
11723
+ {
11724
+ slide: nextSlide,
11725
+ templateElements,
11726
+ canvasSize
11727
+ }
11728
+ ) : /* @__PURE__ */ jsx("div", { className: "flex h-16 items-center justify-center rounded bg-muted/40 text-xs italic text-muted-foreground", children: t("pptx.presenter.endOfPresentation") })
11729
+ ] }),
11730
+ /* @__PURE__ */ jsxs("section", { className: "flex min-h-0 flex-1 flex-col px-4 py-3", children: [
11731
+ /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
11732
+ /* @__PURE__ */ jsx("div", { className: "text-[10px] uppercase tracking-wider text-muted-foreground", children: t("pptx.presenter.speakerNotes") }),
11733
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
11734
+ /* @__PURE__ */ jsx(
11735
+ "button",
11736
+ {
11737
+ type: "button",
11738
+ onClick: () => setFontSize(clampNotesFontSize(fontSize - NOTES_FONT_SIZE_STEP)),
11739
+ disabled: fontSize <= NOTES_FONT_SIZE_MIN,
11740
+ "aria-label": t("pptx.presenter.decreaseFontSize"),
11741
+ children: /* @__PURE__ */ jsx(LuMinus, {})
11742
+ }
11743
+ ),
11744
+ /* @__PURE__ */ jsxs("span", { className: "min-w-8 text-center font-mono text-[10px]", children: [
11745
+ fontSize,
11746
+ "px"
11747
+ ] }),
11748
+ /* @__PURE__ */ jsx(
11749
+ "button",
11750
+ {
11751
+ type: "button",
11752
+ onClick: () => setFontSize(clampNotesFontSize(fontSize + NOTES_FONT_SIZE_STEP)),
11753
+ disabled: fontSize >= NOTES_FONT_SIZE_MAX,
11754
+ "aria-label": t("pptx.presenter.increaseFontSize"),
11755
+ children: /* @__PURE__ */ jsx(LuPlus, {})
11756
+ }
11757
+ )
11758
+ ] })
11759
+ ] }),
11760
+ onUpdateNotes ? /* @__PURE__ */ jsx(
11761
+ "textarea",
11762
+ {
11763
+ className: "min-h-0 flex-1 resize-none rounded border border-border/30 bg-muted/40 px-3 py-2 leading-relaxed",
11764
+ style: { fontSize },
11765
+ value: notesDraft,
11766
+ onChange: (event) => setNotesDraft(event.target.value),
11767
+ onBlur: () => onUpdateNotes(notesDraft),
11768
+ "aria-label": t("pptx.presenter.speakerNotes")
11769
+ }
11770
+ ) : /* @__PURE__ */ jsx(
11771
+ "div",
11772
+ {
11773
+ className: "min-h-0 flex-1 overflow-y-auto whitespace-pre-wrap rounded border border-border/30 bg-muted/40 px-3 py-2 leading-relaxed",
11774
+ style: { fontSize },
11775
+ children: notes
11776
+ }
11777
+ )
11778
+ ] })
11779
+ ] });
11780
+ }
11781
+ function PresenterSlideNavigator(props) {
11782
+ return /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 z-[120] flex flex-col bg-slate-950/98 text-slate-100", children: [
11783
+ /* @__PURE__ */ jsxs("header", { className: "flex items-center justify-between border-b border-white/10 px-6 py-4", children: [
11784
+ /* @__PURE__ */ jsxs("div", { children: [
11785
+ /* @__PURE__ */ jsx("p", { className: "text-xs uppercase tracking-[0.22em] text-sky-300", children: "Slide navigator" }),
11786
+ /* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold", children: "See all slides" })
11787
+ ] }),
11788
+ /* @__PURE__ */ jsx(
11789
+ "button",
11790
+ {
11791
+ className: "rounded-md bg-white/10 px-4 py-2 hover:bg-white/20",
11792
+ onClick: props.onClose,
11793
+ children: "Close"
11794
+ }
11795
+ )
11796
+ ] }),
11797
+ /* @__PURE__ */ jsx("div", { className: "grid flex-1 grid-cols-[repeat(auto-fill,minmax(220px,1fr))] gap-5 overflow-auto p-6", children: props.slides.map((slide, index) => /* @__PURE__ */ jsxs(
11798
+ "button",
11799
+ {
11800
+ className: `group text-left ${index === props.current ? "ring-2 ring-sky-400" : ""} ${slide.hidden ? "opacity-45" : ""}`,
11801
+ onClick: () => props.onSelect(index),
11802
+ children: [
11803
+ /* @__PURE__ */ jsx(
11804
+ ScaledSlidePreview,
11805
+ {
11806
+ slide,
11807
+ templateElements: props.templateElements,
11808
+ canvasSize: props.canvasSize
11809
+ }
11810
+ ),
11811
+ /* @__PURE__ */ jsxs("span", { className: "mt-2 block text-xs tabular-nums text-slate-400", children: [
11812
+ index + 1,
11813
+ slide.hidden ? " - hidden" : ""
11814
+ ] })
11815
+ ]
11816
+ },
11817
+ slide.id ?? index
11818
+ )) })
11819
+ ] });
11820
+ }
11821
+ function usePresenterInk(snapshot, onUpdate) {
11822
+ const drawingId = useRef(null);
11823
+ const point = (event) => {
11824
+ const rect = event.currentTarget.getBoundingClientRect();
11825
+ return {
11826
+ x: (event.clientX - rect.left) / rect.width,
11827
+ y: (event.clientY - rect.top) / rect.height
11828
+ };
11829
+ };
11830
+ const onPointerDown = useCallback(
11831
+ (event) => {
11832
+ const pointer = snapshot.pointer;
11833
+ if (!pointer || pointer.tool === "none" || pointer.tool === "laser") {
11834
+ return;
11835
+ }
11836
+ const nextPoint = point(event);
11837
+ if (pointer.tool === "eraser") {
11838
+ onUpdate({
11839
+ inkStrokes: erasePresentationInkAt(
11840
+ snapshot.inkStrokes ?? [],
11841
+ snapshot.slideIndex,
11842
+ nextPoint
11843
+ )
11844
+ });
11845
+ return;
11846
+ }
11847
+ event.currentTarget.setPointerCapture(event.pointerId);
11848
+ const stroke = {
11849
+ id: crypto.randomUUID(),
11850
+ slideIndex: snapshot.slideIndex,
11851
+ tool: pointer.tool,
11852
+ color: pointer.color,
11853
+ width: pointer.tool === "highlighter" ? 12 : 4,
11854
+ points: [nextPoint]
11855
+ };
11856
+ drawingId.current = stroke.id;
11857
+ onUpdate({ inkStrokes: [...snapshot.inkStrokes ?? [], stroke] });
11858
+ },
11859
+ [onUpdate, snapshot]
11860
+ );
11861
+ const onPointerMove = useCallback(
11862
+ (event) => {
11863
+ const nextPoint = point(event);
11864
+ const pointer = snapshot.pointer;
11865
+ if (!pointer) {
11866
+ return;
11867
+ }
11868
+ if (pointer.tool === "laser") {
11869
+ onUpdate({ pointer: movePresenterPointer(pointer, nextPoint.x, nextPoint.y) });
11870
+ }
11871
+ if (pointer.tool === "eraser" && event.buttons === 1) {
11872
+ onUpdate({
11873
+ inkStrokes: erasePresentationInkAt(
11874
+ snapshot.inkStrokes ?? [],
11875
+ snapshot.slideIndex,
11876
+ nextPoint
11877
+ )
11878
+ });
11879
+ }
11880
+ if (drawingId.current && event.buttons === 1) {
11881
+ onUpdate({
11882
+ inkStrokes: (snapshot.inkStrokes ?? []).map(
11883
+ (stroke) => stroke.id === drawingId.current ? appendPresentationInkPoint(stroke, nextPoint) : stroke
11884
+ )
11885
+ });
11886
+ }
11887
+ },
11888
+ [onUpdate, snapshot]
11889
+ );
11890
+ const onPointerUp = useCallback(() => {
11891
+ drawingId.current = null;
11892
+ }, []);
11893
+ return { onPointerDown, onPointerMove, onPointerUp };
11894
+ }
11291
11895
  function PresenterView({
11292
11896
  slides,
11293
11897
  currentSlideIndex,
@@ -11298,47 +11902,83 @@ function PresenterView({
11298
11902
  onExit,
11299
11903
  onOpenAudienceWindow,
11300
11904
  onCloseAudienceWindow,
11301
- isAudienceWindowOpen
11905
+ isAudienceWindowOpen,
11906
+ snapshot,
11907
+ onNavigateToSlide,
11908
+ onToggleTimer,
11909
+ onResetTimer,
11910
+ onStepZoom,
11911
+ onResetZoom,
11912
+ onSetBlackout,
11913
+ onUpdateSnapshot,
11914
+ onToggleSubtitles,
11915
+ onSwapDisplays,
11916
+ onUpdateNotes
11302
11917
  }) {
11303
11918
  const { t } = useTranslation();
11304
- const [mounted, setMounted] = useState(false);
11305
- useEffect(() => {
11306
- const timer = window.setTimeout(() => setMounted(true), 300);
11307
- return () => window.clearTimeout(timer);
11308
- }, []);
11309
11919
  const [now, setNow] = useState(Date.now());
11310
11920
  useEffect(() => {
11311
11921
  const interval = window.setInterval(() => setNow(Date.now()), 1e3);
11312
11922
  return () => window.clearInterval(interval);
11313
11923
  }, []);
11314
- const elapsed = presentationStartTime ? now - presentationStartTime : 0;
11315
- const [notesFontSize, setNotesFontSize] = useState(NOTES_FONT_SIZE_DEFAULT);
11316
- const increaseNotesFontSize = useCallback(() => {
11317
- setNotesFontSize((prev) => clampNotesFontSize(prev + NOTES_FONT_SIZE_STEP));
11318
- }, []);
11319
- const decreaseNotesFontSize = useCallback(() => {
11320
- setNotesFontSize((prev) => clampNotesFontSize(prev - NOTES_FONT_SIZE_STEP));
11321
- }, []);
11924
+ const elapsed = snapshot.elapsedMs ?? (presentationStartTime ? now - presentationStartTime : 0);
11925
+ const [showSlides, setShowSlides] = useState(false);
11926
+ const ink = usePresenterInk(snapshot, onUpdateSnapshot);
11927
+ const handleCaptionChange = useCallback(
11928
+ (caption) => onUpdateSnapshot({ caption }),
11929
+ [onUpdateSnapshot]
11930
+ );
11322
11931
  const currentSlide = slides[currentSlideIndex];
11323
- const nextSlide = currentSlideIndex + 1 < slides.length ? slides[currentSlideIndex + 1] : void 0;
11324
- const notesText = currentSlide?.notes ?? "";
11325
- const notesSegments = currentSlide?.notesSegments;
11326
- const hasRichNotes = notesSegments && notesSegments.length > 0;
11327
11932
  if (!currentSlide) {
11328
11933
  return /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-50 flex items-center justify-center bg-card text-muted-foreground", children: t("pptx.presenter.noSlides") });
11329
11934
  }
11330
11935
  const TIMER_SEGMENT_MS = 5 * 60 * 1e3;
11331
11936
  const timerProgress = Math.min(100, elapsed % TIMER_SEGMENT_MS / TIMER_SEGMENT_MS * 100);
11332
11937
  const timerSegment = Math.floor(elapsed / TIMER_SEGMENT_MS);
11333
- return /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 z-50 flex flex-col bg-card text-foreground", children: [
11938
+ return /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 z-50 flex flex-col bg-slate-950 text-slate-100", children: [
11939
+ /* @__PURE__ */ jsx(
11940
+ PresenterConsoleToolbar,
11941
+ {
11942
+ snapshot,
11943
+ audienceOpen: Boolean(isAudienceWindowOpen),
11944
+ onToggleAudience: () => isAudienceWindowOpen ? onCloseAudienceWindow?.() : onOpenAudienceWindow?.(),
11945
+ onSwapDisplays,
11946
+ onToggleTimer,
11947
+ onResetTimer,
11948
+ onShowSlides: () => setShowSlides(true),
11949
+ onStepZoom,
11950
+ onResetZoom,
11951
+ onBlackout: onSetBlackout,
11952
+ onPointerTool: (tool) => onUpdateSnapshot({
11953
+ pointer: { ...snapshot.pointer ?? { x: 0.5, y: 0.5, color: "#ef4444" }, tool }
11954
+ }),
11955
+ onToggleSubtitles,
11956
+ onExit
11957
+ }
11958
+ ),
11334
11959
  /* @__PURE__ */ jsxs("div", { className: "flex flex-1 min-h-0", children: [
11335
- /* @__PURE__ */ jsxs("div", { className: "flex-[7] flex flex-col items-center justify-center bg-black p-6 min-w-0", children: [
11336
- /* @__PURE__ */ jsx(
11337
- ScaledSlidePreview,
11960
+ /* @__PURE__ */ jsxs("div", { className: "flex-[7] flex flex-col items-center justify-center bg-black p-6 min-w-0 overflow-hidden", children: [
11961
+ /* @__PURE__ */ jsxs(
11962
+ "div",
11338
11963
  {
11339
- slide: currentSlide,
11340
- templateElements,
11341
- canvasSize
11964
+ className: "relative transition-transform duration-200",
11965
+ style: {
11966
+ transform: `scale(${snapshot.zoom?.scale ?? 1})`,
11967
+ transformOrigin: `${(snapshot.zoom?.originX ?? 0.5) * 100}% ${(snapshot.zoom?.originY ?? 0.5) * 100}%`,
11968
+ touchAction: "none"
11969
+ },
11970
+ ...ink,
11971
+ children: [
11972
+ /* @__PURE__ */ jsx(
11973
+ ScaledSlidePreview,
11974
+ {
11975
+ slide: currentSlide,
11976
+ templateElements,
11977
+ canvasSize
11978
+ }
11979
+ ),
11980
+ /* @__PURE__ */ jsx(PresentationAudienceEffects, { snapshot })
11981
+ ]
11342
11982
  }
11343
11983
  ),
11344
11984
  /* @__PURE__ */ jsx("div", { className: "mt-3 text-xs font-mono tabular-nums text-white/50 select-none", children: t("pptx.presenter.slideLabel", {
@@ -11347,138 +11987,41 @@ function PresenterView({
11347
11987
  defaultValue: `Slide ${currentSlideIndex + 1} of ${slides.length}`
11348
11988
  }) })
11349
11989
  ] }),
11350
- /* @__PURE__ */ jsxs("div", { className: "flex-[3] flex flex-col bg-background border-l border-border min-w-[260px] max-w-[440px]", children: [
11351
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-border/60", children: [
11352
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
11353
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground uppercase tracking-wider", children: t("pptx.presenter.currentTime") }),
11354
- /* @__PURE__ */ jsx("span", { className: "text-lg font-mono tabular-nums text-foreground", children: formatTime(new Date(now)) })
11355
- ] }),
11356
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-end", children: [
11357
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground uppercase tracking-wider", children: t("pptx.presenter.elapsed") }),
11358
- /* @__PURE__ */ jsx("span", { className: "text-lg font-mono tabular-nums text-primary", children: formatElapsed(elapsed) })
11359
- ] }),
11360
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
11361
- onOpenAudienceWindow && mounted && /* @__PURE__ */ jsx(
11362
- "button",
11363
- {
11364
- type: "button",
11365
- onClick: () => {
11366
- if (isAudienceWindowOpen) {
11367
- onCloseAudienceWindow?.();
11368
- } else {
11369
- onOpenAudienceWindow();
11370
- }
11371
- },
11372
- className: "p-1.5 rounded hover:bg-accent text-muted-foreground hover:text-foreground transition-colors",
11373
- title: isAudienceWindowOpen ? t("pptx.presenter.closeAudienceWindow") : t("pptx.presenter.openAudienceWindow"),
11374
- "aria-label": isAudienceWindowOpen ? t("pptx.presenter.closeAudienceWindow") : t("pptx.presenter.openAudienceWindow"),
11375
- children: isAudienceWindowOpen ? /* @__PURE__ */ jsx(LuMonitorOff, { className: "w-5 h-5" }) : /* @__PURE__ */ jsx(LuMonitor, { className: "w-5 h-5" })
11376
- }
11377
- ),
11378
- /* @__PURE__ */ jsx(
11379
- "button",
11380
- {
11381
- type: "button",
11382
- onClick: onExit,
11383
- className: "p-1.5 rounded hover:bg-accent text-muted-foreground hover:text-foreground transition-colors",
11384
- title: t("pptx.presenter.endPresentation"),
11385
- "aria-label": t("pptx.presenter.endPresentation"),
11386
- children: /* @__PURE__ */ jsx(LuX, { className: "w-5 h-5" })
11387
- }
11388
- )
11389
- ] })
11390
- ] }),
11391
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-2 border-b border-border/60", children: [
11392
- /* @__PURE__ */ jsxs(
11393
- "button",
11394
- {
11395
- type: "button",
11396
- onClick: () => onMovePresentationSlide(-1),
11397
- disabled: currentSlideIndex === 0,
11398
- className: "inline-flex items-center gap-1.5 px-3 py-1.5 rounded bg-muted hover:bg-accent disabled:opacity-40 disabled:cursor-not-allowed text-xs transition-colors",
11399
- title: t("pptx.presenter.previousSlide"),
11400
- children: [
11401
- /* @__PURE__ */ jsx(LuChevronLeft, { className: "w-4 h-4" }),
11402
- t("pptx.presenter.prev")
11403
- ]
11404
- }
11405
- ),
11406
- /* @__PURE__ */ jsxs("span", { className: "text-sm font-mono tabular-nums text-foreground", children: [
11407
- currentSlideIndex + 1,
11408
- " / ",
11409
- slides.length
11410
- ] }),
11411
- /* @__PURE__ */ jsxs(
11412
- "button",
11413
- {
11414
- type: "button",
11415
- onClick: () => onMovePresentationSlide(1),
11416
- disabled: currentSlideIndex >= slides.length - 1,
11417
- className: "inline-flex items-center gap-1.5 px-3 py-1.5 rounded bg-muted hover:bg-accent disabled:opacity-40 disabled:cursor-not-allowed text-xs transition-colors",
11418
- title: t("pptx.presenter.nextSlide"),
11419
- children: [
11420
- t("pptx.presenter.next"),
11421
- /* @__PURE__ */ jsx(LuChevronRight, { className: "w-4 h-4" })
11422
- ]
11423
- }
11424
- )
11425
- ] }),
11426
- /* @__PURE__ */ jsxs("div", { className: "px-4 py-3 border-b border-border/60", children: [
11427
- /* @__PURE__ */ jsx("div", { className: "text-[10px] text-muted-foreground uppercase tracking-wider mb-2", children: t("pptx.presenter.nextSlidePreview") }),
11428
- nextSlide ? /* @__PURE__ */ jsx(
11429
- ScaledSlidePreview,
11430
- {
11431
- slide: nextSlide,
11432
- templateElements,
11433
- canvasSize
11434
- }
11435
- ) : /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-16 rounded border border-border/30 bg-muted/40 text-xs text-muted-foreground italic", children: t("pptx.presenter.endOfPresentation") })
11436
- ] }),
11437
- /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col min-h-0 px-4 py-3", children: [
11438
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
11439
- /* @__PURE__ */ jsx("div", { className: "text-[10px] text-muted-foreground uppercase tracking-wider", children: t("pptx.presenter.speakerNotes") }),
11440
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
11441
- /* @__PURE__ */ jsx(
11442
- "button",
11443
- {
11444
- type: "button",
11445
- onClick: decreaseNotesFontSize,
11446
- disabled: notesFontSize <= NOTES_FONT_SIZE_MIN,
11447
- className: "p-0.5 rounded hover:bg-accent text-muted-foreground hover:text-foreground disabled:opacity-30 disabled:cursor-not-allowed transition-colors",
11448
- title: t("pptx.presenter.decreaseFontSize"),
11449
- "aria-label": t("pptx.presenter.decreaseFontSize"),
11450
- children: /* @__PURE__ */ jsx(LuMinus, { className: "w-3.5 h-3.5" })
11451
- }
11452
- ),
11453
- /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-mono tabular-nums text-muted-foreground min-w-[28px] text-center select-none", children: [
11454
- notesFontSize,
11455
- "px"
11456
- ] }),
11457
- /* @__PURE__ */ jsx(
11458
- "button",
11459
- {
11460
- type: "button",
11461
- onClick: increaseNotesFontSize,
11462
- disabled: notesFontSize >= NOTES_FONT_SIZE_MAX,
11463
- className: "p-0.5 rounded hover:bg-accent text-muted-foreground hover:text-foreground disabled:opacity-30 disabled:cursor-not-allowed transition-colors",
11464
- title: t("pptx.presenter.increaseFontSize"),
11465
- "aria-label": t("pptx.presenter.increaseFontSize"),
11466
- children: /* @__PURE__ */ jsx(LuPlus, { className: "w-3.5 h-3.5" })
11467
- }
11468
- )
11469
- ] })
11470
- ] }),
11471
- /* @__PURE__ */ jsx(
11472
- "div",
11473
- {
11474
- className: "flex-1 overflow-y-auto rounded border border-border/30 bg-muted/40 px-3 py-2 text-foreground whitespace-pre-wrap leading-relaxed",
11475
- style: { fontSize: `${notesFontSize}px` },
11476
- children: hasRichNotes ? renderNotesSegments(notesSegments) : notesText.trim().length > 0 ? notesText : /* @__PURE__ */ jsx("span", { className: "italic text-muted-foreground", children: t("pptx.presenter.noNotes") })
11477
- }
11478
- )
11479
- ] })
11480
- ] })
11990
+ /* @__PURE__ */ jsx(
11991
+ PresenterNotesRail,
11992
+ {
11993
+ slides,
11994
+ current: currentSlideIndex,
11995
+ canvasSize,
11996
+ templateElements,
11997
+ now,
11998
+ elapsed,
11999
+ onMove: onMovePresentationSlide,
12000
+ onUpdateNotes
12001
+ }
12002
+ )
11481
12003
  ] }),
12004
+ /* @__PURE__ */ jsx(
12005
+ PresentationSubtitleBar,
12006
+ {
12007
+ visible: Boolean(snapshot.subtitlesVisible),
12008
+ onCaptionChange: handleCaptionChange
12009
+ }
12010
+ ),
12011
+ showSlides && /* @__PURE__ */ jsx(
12012
+ PresenterSlideNavigator,
12013
+ {
12014
+ slides,
12015
+ current: currentSlideIndex,
12016
+ canvasSize,
12017
+ templateElements,
12018
+ onSelect: (index) => {
12019
+ onNavigateToSlide(index);
12020
+ setShowSlides(false);
12021
+ },
12022
+ onClose: () => setShowSlides(false)
12023
+ }
12024
+ ),
11482
12025
  /* @__PURE__ */ jsx(
11483
12026
  "div",
11484
12027
  {
@@ -14615,23 +15158,12 @@ function MobileSheet({
14615
15158
  }) {
14616
15159
  const sheetRef = useRef(null);
14617
15160
  const { dragY, dragging, handlers } = useSheetDismissDrag(onClose);
14618
- useEffect(() => {
14619
- if (!open) {
14620
- return;
14621
- }
14622
- const handleKey = (e) => {
14623
- if (e.key === "Escape") {
14624
- onClose();
14625
- }
14626
- };
14627
- window.addEventListener("keydown", handleKey);
14628
- return () => window.removeEventListener("keydown", handleKey);
14629
- }, [open, onClose]);
15161
+ useModalFocus(open, sheetRef, onClose);
14630
15162
  if (!open) {
14631
15163
  return null;
14632
15164
  }
14633
15165
  const heightStyle = autoHeight ? { maxHeight: "calc(85dvh - env(safe-area-inset-top))" } : fullScreen ? { height: "calc(100dvh - env(safe-area-inset-top))" } : { height: `${Math.round(heightFraction * 100)}dvh` };
14634
- return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex flex-col justify-end", role: "dialog", "aria-modal": "true", children: [
15166
+ return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex flex-col justify-end", children: [
14635
15167
  /* @__PURE__ */ jsx(
14636
15168
  "button",
14637
15169
  {
@@ -14645,6 +15177,10 @@ function MobileSheet({
14645
15177
  "div",
14646
15178
  {
14647
15179
  ref: sheetRef,
15180
+ role: "dialog",
15181
+ "aria-modal": "true",
15182
+ "aria-label": typeof title === "string" ? title : "Mobile panel",
15183
+ tabIndex: -1,
14648
15184
  className: cn(
14649
15185
  "relative bg-background border-t border-border rounded-t-2xl shadow-2xl flex flex-col overflow-hidden",
14650
15186
  "animate-in slide-in-from-bottom duration-200",
@@ -14668,7 +15204,18 @@ function MobileSheet({
14668
15204
  /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center pt-2 pb-1", children: /* @__PURE__ */ jsx("div", { className: "h-1 w-10 rounded-full bg-muted-foreground/40" }) }),
14669
15205
  (title || headerRight) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-4 pb-2 border-b border-border/60", children: [
14670
15206
  /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold text-foreground truncate", children: title }),
14671
- headerRight
15207
+ headerRight,
15208
+ /* @__PURE__ */ jsx(
15209
+ "button",
15210
+ {
15211
+ type: "button",
15212
+ "aria-label": "Close",
15213
+ onPointerDown: (event) => event.stopPropagation(),
15214
+ onClick: onClose,
15215
+ className: "inline-flex h-8 w-8 items-center justify-center rounded text-xl text-muted-foreground hover:bg-accent hover:text-foreground",
15216
+ children: "\xD7"
15217
+ }
15218
+ )
14672
15219
  ] })
14673
15220
  ]
14674
15221
  }
@@ -20702,6 +21249,8 @@ function SmartArtLayoutSwitcher({
20702
21249
  layoutType: updated.layoutType,
20703
21250
  resolvedLayoutType: updated.resolvedLayoutType,
20704
21251
  layout: updated.layout,
21252
+ layoutDirty: updated.layoutDirty,
21253
+ drawingDirty: updated.drawingDirty,
20705
21254
  drawingShapes: updated.drawingShapes
20706
21255
  });
20707
21256
  };
@@ -22312,6 +22861,42 @@ function InspectorCommentsSection({
22312
22861
  ] })
22313
22862
  ] });
22314
22863
  }
22864
+ function InspectorPaneHeader({
22865
+ activeTab,
22866
+ onSetActiveTab,
22867
+ onClose
22868
+ }) {
22869
+ const { t } = useTranslation();
22870
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-2 border-b border-border", children: [
22871
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 rounded bg-muted p-0.5", children: INSPECTOR_TABS.map(({ key, label, icon: Icon }) => /* @__PURE__ */ jsxs(
22872
+ "button",
22873
+ {
22874
+ type: "button",
22875
+ title: label,
22876
+ className: cn(
22877
+ "flex items-center gap-1 px-2 py-1 rounded text-[11px] transition-colors",
22878
+ activeTab === key ? "bg-primary text-white" : "text-muted-foreground hover:text-foreground hover:bg-accent"
22879
+ ),
22880
+ onClick: () => onSetActiveTab(key),
22881
+ children: [
22882
+ /* @__PURE__ */ jsx(Icon, { className: "w-3.5 h-3.5" }),
22883
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: label })
22884
+ ]
22885
+ },
22886
+ key
22887
+ )) }),
22888
+ /* @__PURE__ */ jsx(
22889
+ "button",
22890
+ {
22891
+ type: "button",
22892
+ onClick: onClose,
22893
+ title: t("common.close"),
22894
+ className: "p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted transition-colors",
22895
+ children: /* @__PURE__ */ jsx(LuX, { className: "w-4 h-4" })
22896
+ }
22897
+ )
22898
+ ] });
22899
+ }
22315
22900
  function InfoRow({ label, value }) {
22316
22901
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
22317
22902
  /* @__PURE__ */ jsx("span", { children: label }),
@@ -22696,8 +23281,23 @@ function SlideThemeOverridePanel({
22696
23281
  }) {
22697
23282
  const { t } = useTranslation();
22698
23283
  const override = activeSlide?.clrMapOverride;
22699
- const isOverrideActive = hasNonTrivialOverride(override);
23284
+ const isOverrideActive = override !== void 0;
22700
23285
  const targetSlotOptions = useMemo(() => [...THEME_COLOR_SCHEME_KEYS], []);
23286
+ const commitOverride = useCallback(
23287
+ (nextOverride) => {
23288
+ if (!activeSlide || !theme?.colorScheme) {
23289
+ onUpdateSlide({ clrMapOverride: nextOverride });
23290
+ return;
23291
+ }
23292
+ const nextSlide = applyThemeOverrideToSlide(activeSlide, theme.colorScheme, nextOverride);
23293
+ onUpdateSlide({
23294
+ clrMapOverride: nextSlide.clrMapOverride,
23295
+ backgroundColor: nextSlide.backgroundColor,
23296
+ elements: nextSlide.elements
23297
+ });
23298
+ },
23299
+ [activeSlide, onUpdateSlide, theme?.colorScheme]
23300
+ );
22701
23301
  const handleToggle = useCallback(
22702
23302
  (enabled) => {
22703
23303
  if (enabled) {
@@ -22705,12 +23305,12 @@ function SlideThemeOverridePanel({
22705
23305
  for (const key of COLOR_MAP_ALIAS_KEYS) {
22706
23306
  defaultMap[key] = DEFAULT_COLOR_MAP[key];
22707
23307
  }
22708
- onUpdateSlide({ clrMapOverride: defaultMap });
23308
+ commitOverride(defaultMap);
22709
23309
  } else {
22710
- onUpdateSlide({ clrMapOverride: void 0 });
23310
+ commitOverride(void 0);
22711
23311
  }
22712
23312
  },
22713
- [onUpdateSlide]
23313
+ [commitOverride]
22714
23314
  );
22715
23315
  const handleAliasChange = useCallback(
22716
23316
  (alias, targetSlot) => {
@@ -22721,9 +23321,9 @@ function SlideThemeOverridePanel({
22721
23321
  next[key] = DEFAULT_COLOR_MAP[key];
22722
23322
  }
22723
23323
  }
22724
- onUpdateSlide({ clrMapOverride: next });
23324
+ commitOverride(next);
22725
23325
  },
22726
- [override, onUpdateSlide]
23326
+ [commitOverride, override]
22727
23327
  );
22728
23328
  const resolveSlotColor = useCallback(
22729
23329
  (slot) => {
@@ -23158,6 +23758,35 @@ function TemplateBackgroundCard({
23158
23758
  })()
23159
23759
  ] });
23160
23760
  }
23761
+ var ANIMATION_PANEL_MIN_HEIGHT = 80;
23762
+ var ANIMATION_PANEL_MAX_HEIGHT = 500;
23763
+ var ANIMATION_PANEL_DEFAULT_HEIGHT = 220;
23764
+ function useInspectorPaneState(themeOptions, slideMasters, theme) {
23765
+ const activeThemePath = slideMasters?.[0]?.themePath;
23766
+ const effectiveThemeOptions = useMemo(() => {
23767
+ if (themeOptions.length > 0 || !activeThemePath) {
23768
+ return themeOptions;
23769
+ }
23770
+ return [{ path: activeThemePath, name: theme?.name }];
23771
+ }, [activeThemePath, theme?.name, themeOptions]);
23772
+ const [selectedThemePath, setSelectedThemePath] = useState("");
23773
+ useEffect(() => {
23774
+ setSelectedThemePath(activeThemePath ?? effectiveThemeOptions[0]?.path ?? "");
23775
+ }, [activeThemePath, effectiveThemeOptions]);
23776
+ const [animationPanelHeight, setAnimationPanelHeight] = useState(ANIMATION_PANEL_DEFAULT_HEIGHT);
23777
+ const onResizeAnimationPanel = useCallback((delta) => {
23778
+ setAnimationPanelHeight(
23779
+ (height) => Math.min(ANIMATION_PANEL_MAX_HEIGHT, Math.max(ANIMATION_PANEL_MIN_HEIGHT, height - delta))
23780
+ );
23781
+ }, []);
23782
+ return {
23783
+ animationPanelHeight,
23784
+ effectiveThemeOptions,
23785
+ onResizeAnimationPanel,
23786
+ selectedThemePath,
23787
+ setSelectedThemePath
23788
+ };
23789
+ }
23161
23790
  function InspectorPane(props) {
23162
23791
  const {
23163
23792
  isOpen,
@@ -23222,23 +23851,13 @@ function InspectorPane(props) {
23222
23851
  const hasSelection = selectedElement !== null;
23223
23852
  const { t } = useTranslation();
23224
23853
  const { dragY, handlers: dragHandlers } = useSheetDismissDrag(onClose);
23225
- const [selectedThemePath, setSelectedThemePath] = useState("");
23226
- const ANIM_MIN = 80;
23227
- const ANIM_MAX = 500;
23228
- const ANIM_DEFAULT = 220;
23229
- const [animPanelHeight, setAnimPanelHeight] = useState(ANIM_DEFAULT);
23230
- const onResizeAnim = useCallback((delta) => {
23231
- setAnimPanelHeight((h) => Math.min(ANIM_MAX, Math.max(ANIM_MIN, h - delta)));
23232
- }, []);
23233
- useEffect(() => {
23234
- const activeThemePath = slideMasters?.[0]?.themePath;
23235
- if (activeThemePath && activeThemePath.length > 0) {
23236
- setSelectedThemePath(activeThemePath);
23237
- return;
23238
- }
23239
- const fallback = themeOptions[0]?.path ?? "";
23240
- setSelectedThemePath((previous) => previous || fallback);
23241
- }, [slideMasters, themeOptions]);
23854
+ const {
23855
+ animationPanelHeight,
23856
+ effectiveThemeOptions,
23857
+ onResizeAnimationPanel,
23858
+ selectedThemePath,
23859
+ setSelectedThemePath
23860
+ } = useInspectorPaneState(themeOptions, slideMasters, theme);
23242
23861
  return /* @__PURE__ */ jsxs(Fragment, { children: [
23243
23862
  isOpen && /* @__PURE__ */ jsx(
23244
23863
  "button",
@@ -23280,35 +23899,14 @@ function InspectorPane(props) {
23280
23899
  children: /* @__PURE__ */ jsx("div", { className: "h-1 w-10 rounded-full bg-muted-foreground/40" })
23281
23900
  }
23282
23901
  ),
23283
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-2 border-b border-border", children: [
23284
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 rounded bg-muted p-0.5", children: INSPECTOR_TABS.map(({ key, label, icon: Icon }) => /* @__PURE__ */ jsxs(
23285
- "button",
23286
- {
23287
- type: "button",
23288
- title: label,
23289
- className: cn(
23290
- "flex items-center gap-1 px-2 py-1 rounded text-[11px] transition-colors",
23291
- activeTab === key ? "bg-primary text-white" : "text-muted-foreground hover:text-foreground hover:bg-accent"
23292
- ),
23293
- onClick: () => onSetActiveTab(key),
23294
- children: [
23295
- /* @__PURE__ */ jsx(Icon, { className: "w-3.5 h-3.5" }),
23296
- /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: label })
23297
- ]
23298
- },
23299
- key
23300
- )) }),
23301
- /* @__PURE__ */ jsx(
23302
- "button",
23303
- {
23304
- type: "button",
23305
- onClick: onClose,
23306
- title: t("common.close"),
23307
- className: "p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted transition-colors",
23308
- children: /* @__PURE__ */ jsx(LuX, { className: "w-4 h-4" })
23309
- }
23310
- )
23311
- ] }),
23902
+ /* @__PURE__ */ jsx(
23903
+ InspectorPaneHeader,
23904
+ {
23905
+ activeTab,
23906
+ onSetActiveTab,
23907
+ onClose
23908
+ }
23909
+ ),
23312
23910
  /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto p-3 space-y-3", children: [
23313
23911
  activeTab === "elements" && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
23314
23912
  /* @__PURE__ */ jsx("div", { className: cn(HEADING, "mb-2"), children: t("pptx.inspector.layerOrder") }),
@@ -23361,7 +23959,7 @@ function InspectorPane(props) {
23361
23959
  coreProperties,
23362
23960
  appProperties,
23363
23961
  customProperties,
23364
- themeOptions,
23962
+ themeOptions: effectiveThemeOptions,
23365
23963
  selectedThemePath,
23366
23964
  setSelectedThemePath,
23367
23965
  onApplyTheme,
@@ -23418,12 +24016,12 @@ function InspectorPane(props) {
23418
24016
  )
23419
24017
  ] }),
23420
24018
  hasSelection && selectedElement && activeSlide && /* @__PURE__ */ jsxs(Fragment, { children: [
23421
- /* @__PURE__ */ jsx(ResizeHandle, { direction: "vertical", onResize: onResizeAnim }),
24019
+ /* @__PURE__ */ jsx(ResizeHandle, { direction: "vertical", onResize: onResizeAnimationPanel }),
23422
24020
  /* @__PURE__ */ jsx(
23423
24021
  "div",
23424
24022
  {
23425
24023
  className: "border-t border-border p-3 overflow-y-auto flex-shrink-0",
23426
- style: { height: animPanelHeight },
24024
+ style: { height: animationPanelHeight },
23427
24025
  children: /* @__PURE__ */ jsx(
23428
24026
  AnimationPanel,
23429
24027
  {
@@ -23993,84 +24591,8 @@ function PresentationTouchControls({
23993
24591
  style: { bottom: "calc(env(safe-area-inset-bottom, 0px) + 0.5rem)" },
23994
24592
  children: totalSlides === 0 ? "0 / 0" : `${currentSlideIndex + 1} / ${totalSlides}`
23995
24593
  }
23996
- )
23997
- ] });
23998
- }
23999
- function PresentationSubtitleBar({
24000
- visible
24001
- }) {
24002
- const { t } = useTranslation();
24003
- const [captionText, setCaptionText] = useState("");
24004
- const [supportState, setSupportState] = useState(
24005
- "unknown"
24006
- );
24007
- const recognitionRef = useRef(null);
24008
- const shouldRunRef = useRef(false);
24009
- useEffect(() => {
24010
- if (!visible) {
24011
- shouldRunRef.current = false;
24012
- recognitionRef.current?.stop();
24013
- recognitionRef.current = null;
24014
- setCaptionText("");
24015
- return;
24016
- }
24017
- shouldRunRef.current = true;
24018
- const speechWindow = window;
24019
- const RecognitionCtor = speechWindow.SpeechRecognition ?? speechWindow.webkitSpeechRecognition;
24020
- if (!RecognitionCtor) {
24021
- setSupportState("unsupported");
24022
- return;
24023
- }
24024
- setSupportState("supported");
24025
- const recognition = new RecognitionCtor();
24026
- recognition.continuous = true;
24027
- recognition.interimResults = true;
24028
- recognition.lang = navigator.language || "en-US";
24029
- recognition.onresult = (event) => {
24030
- let finalText = "";
24031
- let interimText = "";
24032
- for (let index = event.resultIndex; index < event.results.length; index += 1) {
24033
- const result = event.results[index];
24034
- const fragment = result?.[0]?.transcript ?? "";
24035
- if (result?.isFinal) {
24036
- finalText += fragment;
24037
- } else {
24038
- interimText += fragment;
24039
- }
24040
- }
24041
- const merged = `${finalText} ${interimText}`.trim();
24042
- if (merged.length > 0) {
24043
- setCaptionText(merged);
24044
- }
24045
- };
24046
- recognition.onerror = () => {
24047
- };
24048
- recognition.onend = () => {
24049
- if (!shouldRunRef.current) {
24050
- return;
24051
- }
24052
- try {
24053
- recognition.start();
24054
- } catch {
24055
- }
24056
- };
24057
- recognitionRef.current = recognition;
24058
- try {
24059
- recognition.start();
24060
- } catch {
24061
- setSupportState("unsupported");
24062
- }
24063
- return () => {
24064
- shouldRunRef.current = false;
24065
- recognition.stop();
24066
- recognitionRef.current = null;
24067
- };
24068
- }, [visible]);
24069
- if (!visible) {
24070
- return null;
24071
- }
24072
- const renderedText = supportState === "unsupported" ? t("pptx.subtitles.notSupported") : captionText.length > 0 ? captionText : t("pptx.subtitles.listening");
24073
- return /* @__PURE__ */ jsx("div", { className: "absolute bottom-14 left-1/2 -translate-x-1/2 z-[70] max-w-[80%] min-w-[300px]", children: /* @__PURE__ */ jsx("div", { className: "px-6 py-3 rounded-lg bg-black/75 backdrop-blur-sm border border-white/10", children: /* @__PURE__ */ jsx("p", { className: "text-center text-[15px] text-white/70 italic", children: renderedText }) }) });
24594
+ )
24595
+ ] });
24074
24596
  }
24075
24597
  function SlideLayer({ slide, templateElements, canvasSize }) {
24076
24598
  const safeWidth = Math.max(canvasSize.width, 1);
@@ -25498,6 +26020,7 @@ function BroadcastDialog({
25498
26020
  const [copied, setCopied] = useState(false);
25499
26021
  const dialogRef = useRef(null);
25500
26022
  const { panelStyle, handlers: dragHandlers } = useModalDismissDrag(onClose);
26023
+ useModalFocus(open, dialogRef, onClose);
25501
26024
  useEffect(() => {
25502
26025
  if (open && !isBroadcasting) {
25503
26026
  const broadcastRoom = defaultRoomId ? `broadcast-${defaultRoomId}` : generateBroadcastRoomId();
@@ -25506,23 +26029,6 @@ function BroadcastDialog({
25506
26029
  setServerUrl(defaultServerUrl ?? DEFAULT_BROADCAST_SERVER_URL);
25507
26030
  }
25508
26031
  }, [open, isBroadcasting, defaultRoomId, defaultUserName, defaultServerUrl]);
25509
- useEffect(() => {
25510
- if (!open) {
25511
- return;
25512
- }
25513
- function handleKeyDown(e) {
25514
- if (e.key === "Escape") {
25515
- onClose();
25516
- }
25517
- }
25518
- document.addEventListener("keydown", handleKeyDown);
25519
- return () => document.removeEventListener("keydown", handleKeyDown);
25520
- }, [open, onClose]);
25521
- useEffect(() => {
25522
- if (open && dialogRef.current) {
25523
- dialogRef.current.focus();
25524
- }
25525
- }, [open]);
25526
26032
  const activeRoomId = isBroadcasting ? collab.config.roomId : roomId;
25527
26033
  const activeServerUrl = isBroadcasting ? collab.config.serverUrl : serverUrl;
25528
26034
  const broadcastUrl = typeof window !== "undefined" ? buildBroadcastViewerUrl(activeRoomId, activeServerUrl, window.location) : activeRoomId;
@@ -25795,37 +26301,265 @@ function ActiveBroadcastView({
25795
26301
  ] })
25796
26302
  ] }, user.clientId)) })
25797
26303
  ] }),
25798
- /* @__PURE__ */ jsx(
25799
- "button",
25800
- {
25801
- type: "button",
25802
- onClick: onStopBroadcast,
25803
- className: "w-full px-3 py-2 rounded border border-red-500/30 bg-red-500/10 hover:bg-red-500/20 text-[12px] text-red-400 font-medium transition-colors",
25804
- children: t("pptx.broadcast.stopBroadcast")
25805
- }
25806
- )
26304
+ /* @__PURE__ */ jsx(
26305
+ "button",
26306
+ {
26307
+ type: "button",
26308
+ onClick: onStopBroadcast,
26309
+ className: "w-full px-3 py-2 rounded border border-red-500/30 bg-red-500/10 hover:bg-red-500/20 text-[12px] text-red-400 font-medium transition-colors",
26310
+ children: t("pptx.broadcast.stopBroadcast")
26311
+ }
26312
+ )
26313
+ ] });
26314
+ }
26315
+ function getInitials3(name) {
26316
+ const parts = name.trim().split(/\s+/u);
26317
+ return parts.length >= 2 ? (parts[0][0] + parts[parts.length - 1][0]).toUpperCase() : name.slice(0, 2).toUpperCase();
26318
+ }
26319
+ function isP2P(config) {
26320
+ return config.transport === "webrtc" || !config.serverUrl?.trim();
26321
+ }
26322
+ function shareUrl(config) {
26323
+ return buildCollaborationShareUrl(
26324
+ config,
26325
+ typeof window === "undefined" ? void 0 : { origin: window.location.origin, pathname: window.location.pathname }
26326
+ );
26327
+ }
26328
+ function ActiveSessionView({
26329
+ collab,
26330
+ activeCollaboration,
26331
+ copied,
26332
+ onCopyRoomId,
26333
+ onStopCollaboration
26334
+ }) {
26335
+ const { t } = useTranslation();
26336
+ const statusColor = collab.status === "connected" ? "text-green-400" : collab.status === "connecting" ? "text-yellow-400" : "text-red-400";
26337
+ const statusIcon = collab.status === "connected" || collab.status === "connecting" ? /* @__PURE__ */ jsx(LuWifi, { className: "w-4 h-4" }) : /* @__PURE__ */ jsx(LuWifiOff, { className: "w-4 h-4" });
26338
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
26339
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
26340
+ /* @__PURE__ */ jsx("span", { className: statusColor, children: statusIcon }),
26341
+ /* @__PURE__ */ jsx("span", { className: "text-[13px] font-medium text-foreground capitalize", children: collab.status }),
26342
+ /* @__PURE__ */ jsxs("span", { className: "text-[12px] text-muted-foreground ml-auto flex items-center gap-1", children: [
26343
+ /* @__PURE__ */ jsx(LuUsers, { className: "w-3.5 h-3.5" }),
26344
+ t("pptx.collaboration.userCount", { count: collab.connectedCount })
26345
+ ] })
26346
+ ] }),
26347
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
26348
+ /* @__PURE__ */ jsx("label", { className: "block text-[12px] font-medium text-foreground", children: t("pptx.share.shareLink") }),
26349
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
26350
+ /* @__PURE__ */ jsx("div", { className: "flex-1 px-3 py-1.5 rounded border border-border bg-background text-[11px] text-foreground select-all font-mono truncate", children: shareUrl(collab.config) }),
26351
+ /* @__PURE__ */ jsx(
26352
+ "button",
26353
+ {
26354
+ type: "button",
26355
+ onClick: onCopyRoomId,
26356
+ className: "flex items-center gap-1 px-2.5 py-1.5 rounded border border-border bg-muted hover:bg-accent text-[12px] text-foreground transition-colors shrink-0",
26357
+ title: t("pptx.share.copyLink"),
26358
+ children: copied ? /* @__PURE__ */ jsxs(Fragment, { children: [
26359
+ /* @__PURE__ */ jsx(LuCheck, { className: "w-3.5 h-3.5 text-green-400" }),
26360
+ /* @__PURE__ */ jsx("span", { children: t("pptx.share.copied") })
26361
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
26362
+ /* @__PURE__ */ jsx(LuCopy, { className: "w-3.5 h-3.5" }),
26363
+ /* @__PURE__ */ jsx("span", { children: t("pptx.share.copyUrl") })
26364
+ ] })
26365
+ }
26366
+ )
26367
+ ] }),
26368
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t("pptx.share.shareHint") })
26369
+ ] }),
26370
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-[11px] text-muted-foreground", children: [
26371
+ /* @__PURE__ */ jsxs("span", { children: [
26372
+ t("pptx.share.room"),
26373
+ " ",
26374
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-foreground", children: collab.config.roomId })
26375
+ ] }),
26376
+ /* @__PURE__ */ jsxs("span", { children: [
26377
+ t("pptx.share.server"),
26378
+ " ",
26379
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-foreground", children: isP2P(collab.config) ? t("pptx.share.p2pServerValue") : collab.config.serverUrl })
26380
+ ] })
26381
+ ] }),
26382
+ collab.remoteUsers.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
26383
+ /* @__PURE__ */ jsx("label", { className: "block text-[12px] font-medium text-foreground", children: t("pptx.share.connectedUsers") }),
26384
+ /* @__PURE__ */ jsxs("div", { className: "rounded border border-border bg-background divide-y divide-border max-h-[140px] overflow-y-auto", children: [
26385
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [
26386
+ /* @__PURE__ */ jsx(
26387
+ "div",
26388
+ {
26389
+ className: "w-6 h-6 rounded-full flex items-center justify-center text-[9px] font-semibold text-white shrink-0",
26390
+ style: { backgroundColor: collab.config.userColor ?? "#6366f1" },
26391
+ children: getInitials3(activeCollaboration?.userName ?? collab.config.userName)
26392
+ }
26393
+ ),
26394
+ /* @__PURE__ */ jsx("span", { className: "text-[12px] text-foreground truncate", children: activeCollaboration?.userName ?? collab.config.userName }),
26395
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground ml-auto", children: t("pptx.share.you") })
26396
+ ] }),
26397
+ collab.remoteUsers.map((user) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [
26398
+ /* @__PURE__ */ jsx(
26399
+ "div",
26400
+ {
26401
+ className: "w-6 h-6 rounded-full flex items-center justify-center text-[9px] font-semibold text-white shrink-0",
26402
+ style: { backgroundColor: user.userColor },
26403
+ children: user.userAvatar ? /* @__PURE__ */ jsx(
26404
+ "img",
26405
+ {
26406
+ src: user.userAvatar,
26407
+ alt: "",
26408
+ className: "w-full h-full rounded-full object-cover"
26409
+ }
26410
+ ) : getInitials3(user.userName)
26411
+ }
26412
+ ),
26413
+ /* @__PURE__ */ jsx("span", { className: "text-[12px] text-foreground truncate", children: user.userName }),
26414
+ /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground ml-auto", children: [
26415
+ "Slide ",
26416
+ user.activeSlideIndex + 1
26417
+ ] })
26418
+ ] }, user.clientId))
26419
+ ] })
26420
+ ] }),
26421
+ onStopCollaboration && /* @__PURE__ */ jsx(
26422
+ "button",
26423
+ {
26424
+ type: "button",
26425
+ onClick: onStopCollaboration,
26426
+ className: "w-full px-3 py-2 rounded border border-red-500/30 bg-red-500/10 hover:bg-red-500/20 text-[12px] text-red-400 font-medium transition-colors",
26427
+ children: t("pptx.share.stopSharing")
26428
+ }
26429
+ )
26430
+ ] });
26431
+ }
26432
+ function StartSessionForm({
26433
+ mode,
26434
+ invitation,
26435
+ roomId,
26436
+ userName,
26437
+ serverUrl,
26438
+ onRoomIdChange,
26439
+ onUserNameChange,
26440
+ onServerUrlChange,
26441
+ onModeChange,
26442
+ onInvitationChange,
26443
+ preconfigured,
26444
+ connectionFailed,
26445
+ onRetry
26446
+ }) {
26447
+ const { t } = useTranslation();
26448
+ const inputReadOnlyClass = preconfigured ? " opacity-70 cursor-not-allowed" : "";
26449
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
26450
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-1 rounded-lg bg-muted p-1", role: "tablist", children: ["create", "join"].map((candidate) => /* @__PURE__ */ jsx(
26451
+ "button",
26452
+ {
26453
+ type: "button",
26454
+ role: "tab",
26455
+ "aria-selected": mode === candidate,
26456
+ onClick: () => onModeChange(candidate),
26457
+ className: `rounded-md px-3 py-1.5 text-[12px] font-medium transition-colors ${mode === candidate ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
26458
+ children: t(candidate === "create" ? "pptx.share.createSession" : "pptx.share.joinSession")
26459
+ },
26460
+ candidate
26461
+ )) }),
26462
+ /* @__PURE__ */ jsx("p", { className: "text-[13px] text-muted-foreground leading-relaxed", children: mode === "join" ? t("pptx.share.joinDescription") : preconfigured ? t("pptx.share.preconfiguredDescription") : t("pptx.share.description") }),
26463
+ connectionFailed && /* @__PURE__ */ jsxs(
26464
+ "div",
26465
+ {
26466
+ role: "alert",
26467
+ className: "flex items-start gap-2 rounded border border-red-500/30 bg-red-500/10 px-3 py-2 text-[12px] text-red-400",
26468
+ children: [
26469
+ /* @__PURE__ */ jsx(LuWifiOff, { className: "w-4 h-4 mt-0.5 shrink-0" }),
26470
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-1", children: [
26471
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: t("pptx.share.connectionError") }),
26472
+ onRetry && /* @__PURE__ */ jsx(
26473
+ "button",
26474
+ {
26475
+ type: "button",
26476
+ onClick: onRetry,
26477
+ className: "underline underline-offset-2 hover:no-underline",
26478
+ children: t("pptx.collaboration.retry")
26479
+ }
26480
+ )
26481
+ ] })
26482
+ ]
26483
+ }
26484
+ ),
26485
+ mode === "join" ? /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
26486
+ /* @__PURE__ */ jsx(
26487
+ "label",
26488
+ {
26489
+ htmlFor: "share-invitation",
26490
+ className: "block text-[12px] font-medium text-foreground",
26491
+ children: t("pptx.share.invitationLabel")
26492
+ }
26493
+ ),
26494
+ /* @__PURE__ */ jsx(
26495
+ "input",
26496
+ {
26497
+ id: "share-invitation",
26498
+ type: "text",
26499
+ value: invitation,
26500
+ onChange: (event) => onInvitationChange(event.target.value),
26501
+ placeholder: t("pptx.share.invitationPlaceholder"),
26502
+ className: "w-full px-3 py-1.5 rounded border border-border bg-background text-foreground text-[13px] placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary"
26503
+ }
26504
+ ),
26505
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t("pptx.share.invitationHint") })
26506
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
26507
+ /* @__PURE__ */ jsx("label", { htmlFor: "share-room-id", className: "block text-[12px] font-medium text-foreground", children: t("pptx.share.sessionName") }),
26508
+ /* @__PURE__ */ jsx(
26509
+ "input",
26510
+ {
26511
+ id: "share-room-id",
26512
+ type: "text",
26513
+ "aria-label": t("pptx.share.sessionName"),
26514
+ value: roomId,
26515
+ onChange: (e) => onRoomIdChange(e.target.value),
26516
+ readOnly: preconfigured,
26517
+ placeholder: t("pptx.share.sessionPlaceholder"),
26518
+ className: `w-full px-3 py-1.5 rounded border border-border bg-background text-foreground text-[13px] placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary${inputReadOnlyClass}`
26519
+ }
26520
+ ),
26521
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t("pptx.share.sessionHint") })
26522
+ ] }),
26523
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
26524
+ /* @__PURE__ */ jsx("label", { htmlFor: "share-user-name", className: "block text-[12px] font-medium text-foreground", children: t("pptx.share.displayName") }),
26525
+ /* @__PURE__ */ jsx(
26526
+ "input",
26527
+ {
26528
+ id: "share-user-name",
26529
+ type: "text",
26530
+ "aria-label": t("pptx.share.displayName"),
26531
+ value: userName,
26532
+ onChange: (e) => onUserNameChange(e.target.value),
26533
+ readOnly: preconfigured,
26534
+ placeholder: t("pptx.share.namePlaceholder"),
26535
+ className: `w-full px-3 py-1.5 rounded border border-border bg-background text-foreground text-[13px] placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary${inputReadOnlyClass}`
26536
+ }
26537
+ )
26538
+ ] }),
26539
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
26540
+ /* @__PURE__ */ jsx("label", { htmlFor: "share-server-url", className: "block text-[12px] font-medium text-foreground", children: t("pptx.share.serverLabel") }),
26541
+ /* @__PURE__ */ jsx(
26542
+ "input",
26543
+ {
26544
+ id: "share-server-url",
26545
+ type: "text",
26546
+ "aria-label": t("pptx.share.serverLabel"),
26547
+ value: serverUrl,
26548
+ onChange: (e) => onServerUrlChange(e.target.value),
26549
+ readOnly: preconfigured,
26550
+ placeholder: t("pptx.share.serverPlaceholder"),
26551
+ className: `w-full px-3 py-1.5 rounded border border-border bg-background text-foreground text-[13px] placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary${inputReadOnlyClass}`
26552
+ }
26553
+ )
26554
+ ] }),
26555
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: serverUrl.trim().length === 0 ? t("pptx.share.p2pHint") : t("pptx.share.serverHint") })
25807
26556
  ] });
25808
26557
  }
25809
- function getInitials3(name) {
25810
- const parts = name.trim().split(/\s+/u);
25811
- if (parts.length >= 2) {
25812
- return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
25813
- }
25814
- return name.slice(0, 2).toUpperCase();
25815
- }
25816
- function isP2P(config) {
25817
- return config.transport === "webrtc" || !config.serverUrl?.trim();
25818
- }
25819
- function buildCollabShareUrl(config) {
25820
- if (typeof window === "undefined") {
25821
- return config.roomId;
25822
- }
25823
- const base = `${window.location.origin}${window.location.pathname}`;
25824
- const room = encodeURIComponent(config.roomId);
25825
- if (isP2P(config)) {
25826
- return `${base}?room=${room}&transport=webrtc`;
25827
- }
25828
- return `${base}?room=${room}&server=${encodeURIComponent(config.serverUrl ?? "")}`;
26558
+ function shareUrl2(config) {
26559
+ return buildCollaborationShareUrl(
26560
+ config,
26561
+ typeof window === "undefined" ? void 0 : { origin: window.location.origin, pathname: window.location.pathname }
26562
+ );
25829
26563
  }
25830
26564
  function ShareDialog({
25831
26565
  open,
@@ -25844,9 +26578,12 @@ function ShareDialog({
25844
26578
  const [roomId, setRoomId] = useState(defaultRoomId ?? "");
25845
26579
  const [userName, setUserName] = useState(defaultUserName ?? "");
25846
26580
  const [serverUrl, setServerUrl] = useState(defaultServerUrl ?? "");
26581
+ const [mode, setMode] = useState("create");
26582
+ const [invitation, setInvitation] = useState("");
25847
26583
  const [copied, setCopied] = useState(false);
25848
26584
  const dialogRef = useRef(null);
25849
26585
  const { panelStyle, handlers: dragHandlers } = useModalDismissDrag(onClose);
26586
+ useModalFocus(open, dialogRef, onClose);
25850
26587
  useEffect(() => {
25851
26588
  if (activeCollaboration) {
25852
26589
  setRoomId(activeCollaboration.roomId);
@@ -25854,45 +26591,21 @@ function ShareDialog({
25854
26591
  setServerUrl(activeCollaboration.serverUrl);
25855
26592
  }
25856
26593
  }, [activeCollaboration]);
25857
- useEffect(() => {
25858
- if (!open) {
25859
- return;
25860
- }
25861
- function handleKeyDown(e) {
25862
- if (e.key === "Escape") {
25863
- onClose();
25864
- }
25865
- }
25866
- document.addEventListener("keydown", handleKeyDown);
25867
- return () => document.removeEventListener("keydown", handleKeyDown);
25868
- }, [open, onClose]);
25869
- useEffect(() => {
25870
- if (open && dialogRef.current) {
25871
- dialogRef.current.focus();
25872
- }
25873
- }, [open]);
25874
26594
  const handleCopyRoomId = useCallback(() => {
25875
26595
  const config = activeCollaboration ?? { roomId, serverUrl };
25876
- void navigator.clipboard.writeText(buildCollabShareUrl(config)).then(() => {
26596
+ void navigator.clipboard.writeText(shareUrl2(config)).then(() => {
25877
26597
  setCopied(true);
25878
26598
  setTimeout(() => setCopied(false), 2e3);
25879
26599
  return void 0;
25880
26600
  });
25881
26601
  }, [activeCollaboration, roomId, serverUrl]);
25882
26602
  const handleStartSharing = useCallback(() => {
25883
- if (!roomId.trim() || !userName.trim()) {
25884
- return;
26603
+ const config = mode === "join" ? buildJoinCollaborationConfig({ invitation, userName, serverUrl }) : buildCreateCollaborationConfig({ roomId, userName, serverUrl });
26604
+ if (config) {
26605
+ onStartCollaboration?.(config);
25885
26606
  }
25886
- const trimmedServer = serverUrl.trim();
25887
- onStartCollaboration?.({
25888
- roomId: roomId.trim(),
25889
- serverUrl: trimmedServer,
25890
- userName: userName.trim(),
25891
- // A blank server selects the serverless peer-to-peer transport.
25892
- transport: resolveTransportForServerUrl(trimmedServer)
25893
- });
25894
- }, [roomId, userName, serverUrl, onStartCollaboration]);
25895
- const canStart = roomId.trim().length > 0 && userName.trim().length > 0;
26607
+ }, [invitation, mode, roomId, userName, serverUrl, onStartCollaboration]);
26608
+ const canStart = (mode === "join" ? buildJoinCollaborationConfig({ invitation, userName, serverUrl }) : buildCreateCollaborationConfig({ roomId, userName, serverUrl })) !== null;
25896
26609
  if (!open) {
25897
26610
  return null;
25898
26611
  }
@@ -25955,12 +26668,16 @@ function ShareDialog({
25955
26668
  ) : /* @__PURE__ */ jsx(
25956
26669
  StartSessionForm,
25957
26670
  {
26671
+ mode,
26672
+ invitation,
25958
26673
  roomId,
25959
26674
  userName,
25960
26675
  serverUrl,
25961
26676
  onRoomIdChange: setRoomId,
25962
26677
  onUserNameChange: setUserName,
25963
26678
  onServerUrlChange: setServerUrl,
26679
+ onModeChange: setMode,
26680
+ onInvitationChange: setInvitation,
25964
26681
  preconfigured,
25965
26682
  connectionFailed: collab?.status === "error",
25966
26683
  onRetry: collab?.retry
@@ -25983,7 +26700,7 @@ function ShareDialog({
25983
26700
  disabled: !canStart,
25984
26701
  onClick: handleStartSharing,
25985
26702
  className: "px-3 py-1.5 rounded bg-primary hover:bg-primary/90 text-[12px] text-white transition-colors disabled:opacity-40 disabled:cursor-not-allowed",
25986
- children: t("pptx.share.startSharing")
26703
+ children: t(mode === "join" ? "pptx.share.joinSession" : "pptx.share.startSharing")
25987
26704
  }
25988
26705
  )
25989
26706
  ] })
@@ -25994,199 +26711,6 @@ function ShareDialog({
25994
26711
  )
25995
26712
  ] });
25996
26713
  }
25997
- function StartSessionForm({
25998
- roomId,
25999
- userName,
26000
- serverUrl,
26001
- onRoomIdChange,
26002
- onUserNameChange,
26003
- onServerUrlChange,
26004
- preconfigured,
26005
- connectionFailed,
26006
- onRetry
26007
- }) {
26008
- const { t } = useTranslation();
26009
- const inputReadOnlyClass = preconfigured ? " opacity-70 cursor-not-allowed" : "";
26010
- return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
26011
- /* @__PURE__ */ jsx("p", { className: "text-[13px] text-muted-foreground leading-relaxed", children: preconfigured ? t("pptx.share.preconfiguredDescription") : t("pptx.share.description") }),
26012
- connectionFailed && /* @__PURE__ */ jsxs(
26013
- "div",
26014
- {
26015
- role: "alert",
26016
- className: "flex items-start gap-2 rounded border border-red-500/30 bg-red-500/10 px-3 py-2 text-[12px] text-red-400",
26017
- children: [
26018
- /* @__PURE__ */ jsx(LuWifiOff, { className: "w-4 h-4 mt-0.5 shrink-0" }),
26019
- /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-1", children: [
26020
- /* @__PURE__ */ jsx("p", { className: "font-medium", children: t("pptx.share.connectionError") }),
26021
- onRetry && /* @__PURE__ */ jsx(
26022
- "button",
26023
- {
26024
- type: "button",
26025
- onClick: onRetry,
26026
- className: "underline underline-offset-2 hover:no-underline",
26027
- children: t("pptx.collaboration.retry")
26028
- }
26029
- )
26030
- ] })
26031
- ]
26032
- }
26033
- ),
26034
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
26035
- /* @__PURE__ */ jsx("label", { htmlFor: "share-room-id", className: "block text-[12px] font-medium text-foreground", children: t("pptx.share.sessionName") }),
26036
- /* @__PURE__ */ jsx(
26037
- "input",
26038
- {
26039
- id: "share-room-id",
26040
- type: "text",
26041
- "aria-label": t("pptx.share.sessionName"),
26042
- value: roomId,
26043
- onChange: (e) => onRoomIdChange(e.target.value),
26044
- readOnly: preconfigured,
26045
- placeholder: t("pptx.share.sessionPlaceholder"),
26046
- className: `w-full px-3 py-1.5 rounded border border-border bg-background text-foreground text-[13px] placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary${inputReadOnlyClass}`
26047
- }
26048
- ),
26049
- /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t("pptx.share.sessionHint") })
26050
- ] }),
26051
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
26052
- /* @__PURE__ */ jsx("label", { htmlFor: "share-user-name", className: "block text-[12px] font-medium text-foreground", children: t("pptx.share.displayName") }),
26053
- /* @__PURE__ */ jsx(
26054
- "input",
26055
- {
26056
- id: "share-user-name",
26057
- type: "text",
26058
- "aria-label": t("pptx.share.displayName"),
26059
- value: userName,
26060
- onChange: (e) => onUserNameChange(e.target.value),
26061
- readOnly: preconfigured,
26062
- placeholder: t("pptx.share.namePlaceholder"),
26063
- className: `w-full px-3 py-1.5 rounded border border-border bg-background text-foreground text-[13px] placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary${inputReadOnlyClass}`
26064
- }
26065
- )
26066
- ] }),
26067
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
26068
- /* @__PURE__ */ jsx("label", { htmlFor: "share-server-url", className: "block text-[12px] font-medium text-foreground", children: t("pptx.share.serverLabel") }),
26069
- /* @__PURE__ */ jsx(
26070
- "input",
26071
- {
26072
- id: "share-server-url",
26073
- type: "text",
26074
- "aria-label": t("pptx.share.serverLabel"),
26075
- value: serverUrl,
26076
- onChange: (e) => onServerUrlChange(e.target.value),
26077
- readOnly: preconfigured,
26078
- placeholder: t("pptx.share.serverPlaceholder"),
26079
- className: `w-full px-3 py-1.5 rounded border border-border bg-background text-foreground text-[13px] placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary${inputReadOnlyClass}`
26080
- }
26081
- )
26082
- ] }),
26083
- /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: serverUrl.trim().length === 0 ? t("pptx.share.p2pHint") : t("pptx.share.serverHint") })
26084
- ] });
26085
- }
26086
- function ActiveSessionView({
26087
- collab,
26088
- activeCollaboration,
26089
- copied,
26090
- onCopyRoomId,
26091
- onStopCollaboration
26092
- }) {
26093
- const { t } = useTranslation();
26094
- const statusColor = collab.status === "connected" ? "text-green-400" : collab.status === "connecting" ? "text-yellow-400" : "text-red-400";
26095
- const statusIcon = collab.status === "connected" || collab.status === "connecting" ? /* @__PURE__ */ jsx(LuWifi, { className: "w-4 h-4" }) : /* @__PURE__ */ jsx(LuWifiOff, { className: "w-4 h-4" });
26096
- return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
26097
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
26098
- /* @__PURE__ */ jsx("span", { className: statusColor, children: statusIcon }),
26099
- /* @__PURE__ */ jsx("span", { className: "text-[13px] font-medium text-foreground capitalize", children: collab.status }),
26100
- /* @__PURE__ */ jsxs("span", { className: "text-[12px] text-muted-foreground ml-auto flex items-center gap-1", children: [
26101
- /* @__PURE__ */ jsx(LuUsers, { className: "w-3.5 h-3.5" }),
26102
- t("pptx.collaboration.userCount", { count: collab.connectedCount })
26103
- ] })
26104
- ] }),
26105
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
26106
- /* @__PURE__ */ jsx("label", { className: "block text-[12px] font-medium text-foreground", children: t("pptx.share.shareLink") }),
26107
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
26108
- /* @__PURE__ */ jsx("div", { className: "flex-1 px-3 py-1.5 rounded border border-border bg-background text-[11px] text-foreground select-all font-mono truncate", children: buildCollabShareUrl(collab.config) }),
26109
- /* @__PURE__ */ jsx(
26110
- "button",
26111
- {
26112
- type: "button",
26113
- onClick: onCopyRoomId,
26114
- className: "flex items-center gap-1 px-2.5 py-1.5 rounded border border-border bg-muted hover:bg-accent text-[12px] text-foreground transition-colors shrink-0",
26115
- title: t("pptx.share.copyLink"),
26116
- children: copied ? /* @__PURE__ */ jsxs(Fragment, { children: [
26117
- /* @__PURE__ */ jsx(LuCheck, { className: "w-3.5 h-3.5 text-green-400" }),
26118
- /* @__PURE__ */ jsx("span", { children: t("pptx.share.copied") })
26119
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
26120
- /* @__PURE__ */ jsx(LuCopy, { className: "w-3.5 h-3.5" }),
26121
- /* @__PURE__ */ jsx("span", { children: t("pptx.share.copyUrl") })
26122
- ] })
26123
- }
26124
- )
26125
- ] }),
26126
- /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t("pptx.share.shareHint") })
26127
- ] }),
26128
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-[11px] text-muted-foreground", children: [
26129
- /* @__PURE__ */ jsxs("span", { children: [
26130
- t("pptx.share.room"),
26131
- " ",
26132
- /* @__PURE__ */ jsx("code", { className: "font-mono text-foreground", children: collab.config.roomId })
26133
- ] }),
26134
- /* @__PURE__ */ jsxs("span", { children: [
26135
- t("pptx.share.server"),
26136
- " ",
26137
- /* @__PURE__ */ jsx("code", { className: "font-mono text-foreground", children: isP2P(collab.config) ? t("pptx.share.p2pServerValue") : collab.config.serverUrl })
26138
- ] })
26139
- ] }),
26140
- collab.remoteUsers.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
26141
- /* @__PURE__ */ jsx("label", { className: "block text-[12px] font-medium text-foreground", children: t("pptx.share.connectedUsers") }),
26142
- /* @__PURE__ */ jsxs("div", { className: "rounded border border-border bg-background divide-y divide-border max-h-[140px] overflow-y-auto", children: [
26143
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [
26144
- /* @__PURE__ */ jsx(
26145
- "div",
26146
- {
26147
- className: "w-6 h-6 rounded-full flex items-center justify-center text-[9px] font-semibold text-white shrink-0",
26148
- style: { backgroundColor: collab.config.userColor ?? "#6366f1" },
26149
- children: getInitials3(activeCollaboration?.userName ?? collab.config.userName)
26150
- }
26151
- ),
26152
- /* @__PURE__ */ jsx("span", { className: "text-[12px] text-foreground truncate", children: activeCollaboration?.userName ?? collab.config.userName }),
26153
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground ml-auto", children: t("pptx.share.you") })
26154
- ] }),
26155
- collab.remoteUsers.map((user) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [
26156
- /* @__PURE__ */ jsx(
26157
- "div",
26158
- {
26159
- className: "w-6 h-6 rounded-full flex items-center justify-center text-[9px] font-semibold text-white shrink-0",
26160
- style: { backgroundColor: user.userColor },
26161
- children: user.userAvatar ? /* @__PURE__ */ jsx(
26162
- "img",
26163
- {
26164
- src: user.userAvatar,
26165
- alt: "",
26166
- className: "w-full h-full rounded-full object-cover"
26167
- }
26168
- ) : getInitials3(user.userName)
26169
- }
26170
- ),
26171
- /* @__PURE__ */ jsx("span", { className: "text-[12px] text-foreground truncate", children: user.userName }),
26172
- /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground ml-auto", children: [
26173
- "Slide ",
26174
- user.activeSlideIndex + 1
26175
- ] })
26176
- ] }, user.clientId))
26177
- ] })
26178
- ] }),
26179
- onStopCollaboration && /* @__PURE__ */ jsx(
26180
- "button",
26181
- {
26182
- type: "button",
26183
- onClick: onStopCollaboration,
26184
- className: "w-full px-3 py-2 rounded border border-red-500/30 bg-red-500/10 hover:bg-red-500/20 text-[12px] text-red-400 font-medium transition-colors",
26185
- children: t("pptx.share.stopSharing")
26186
- }
26187
- )
26188
- ] });
26189
- }
26190
26714
  function HyperlinkTabContent({
26191
26715
  targetType,
26192
26716
  url,
@@ -27808,7 +28332,7 @@ function SlideCanvas({
27808
28332
  // pinch-zoom. View/present mode keeps the default so the slide can
27809
28333
  // still be scrolled and swipe-navigated.
27810
28334
  touchAction: isEditableCanvas ? "none" : void 0,
27811
- ...getSlideBackgroundStyle(activeSlide)
28335
+ ...getReactSlideBackgroundStyle(activeSlide)
27812
28336
  },
27813
28337
  onClick: handleStageClick,
27814
28338
  onDoubleClick: handleStageDblClick,
@@ -28988,10 +29512,11 @@ function EquationEditorDialog({
28988
29512
  const [latex, setLatex] = useState(initialLatex);
28989
29513
  const deferredLatex = useDeferredValue(latex);
28990
29514
  const textareaRef = useRef(null);
29515
+ const dialogRef = useRef(null);
29516
+ useModalFocus(isOpen, dialogRef, onClose, textareaRef);
28991
29517
  useEffect(() => {
28992
29518
  if (isOpen) {
28993
29519
  setLatex(initialLatex);
28994
- setTimeout(() => textareaRef.current?.focus(), 50);
28995
29520
  }
28996
29521
  }, [isOpen, initialLatex]);
28997
29522
  const { mathml, omml } = useMemo(() => {
@@ -29055,11 +29580,13 @@ function EquationEditorDialog({
29055
29580
  children: /* @__PURE__ */ jsxs(
29056
29581
  "div",
29057
29582
  {
29583
+ ref: dialogRef,
29058
29584
  style: panelStyle,
29059
29585
  className: "bg-background border border-border rounded-xl shadow-2xl w-[640px] max-h-[85vh] flex flex-col overflow-hidden max-md:fixed max-md:inset-x-0 max-md:bottom-0 max-md:top-auto max-md:w-full max-md:max-h-[88dvh] max-md:rounded-t-2xl max-md:rounded-b-none max-md:border-x-0 max-md:border-b-0 max-md:pb-[max(env(safe-area-inset-bottom),0px)]",
29060
29586
  onClick: (e) => e.stopPropagation(),
29061
29587
  role: "dialog",
29062
29588
  "aria-modal": "true",
29589
+ tabIndex: -1,
29063
29590
  "aria-label": isEditing ? t("pptx.equation.editTitle", "Edit Equation") : t("pptx.equation.insertTitle", "Insert Equation"),
29064
29591
  children: [
29065
29592
  /* @__PURE__ */ jsxs(
@@ -29075,6 +29602,7 @@ function EquationEditorDialog({
29075
29602
  type: "button",
29076
29603
  onClick: onClose,
29077
29604
  className: "p-1 rounded hover:bg-accent text-muted-foreground hover:text-foreground transition-colors",
29605
+ "aria-label": t("pptx.settings.close"),
29078
29606
  children: /* @__PURE__ */ jsx(LuX, { className: "w-4 h-4" })
29079
29607
  }
29080
29608
  )
@@ -29209,6 +29737,8 @@ function InsertSmartArtDialog({
29209
29737
  const { panelStyle, handlers: dragHandlers } = useModalDismissDrag(onClose);
29210
29738
  const [activeCategory, setActiveCategory] = useState("list");
29211
29739
  const [selectedLayout, setSelectedLayout] = useState(null);
29740
+ const dialogRef = useRef(null);
29741
+ useModalFocus(isOpen, dialogRef, onClose);
29212
29742
  const filteredPresets = PRESETS.filter((p) => p.category === activeCategory);
29213
29743
  const handleInsert = useCallback(() => {
29214
29744
  if (!selectedLayout) {
@@ -29243,11 +29773,13 @@ function InsertSmartArtDialog({
29243
29773
  children: /* @__PURE__ */ jsxs(
29244
29774
  "div",
29245
29775
  {
29776
+ ref: dialogRef,
29246
29777
  style: panelStyle,
29247
29778
  className: "pointer-events-auto w-[600px] max-w-[90vw] max-h-[80vh] rounded-lg border border-border bg-background shadow-2xl flex flex-col max-md:fixed max-md:inset-x-0 max-md:bottom-0 max-md:top-auto max-md:w-full max-md:max-w-none max-md:max-h-[88dvh] max-md:rounded-t-2xl max-md:rounded-b-none max-md:border-x-0 max-md:border-b-0 max-md:pb-[max(env(safe-area-inset-bottom),0px)]",
29248
29779
  role: "dialog",
29249
29780
  "aria-modal": "true",
29250
29781
  "aria-label": t("pptx.smartart.insertTitle"),
29782
+ tabIndex: -1,
29251
29783
  children: [
29252
29784
  /* @__PURE__ */ jsxs(
29253
29785
  "div",
@@ -29546,9 +30078,19 @@ function ViewerDialogGroup(props) {
29546
30078
  ] });
29547
30079
  }
29548
30080
  function ViewerPresentationLayer(props) {
29549
- const { mode, slides, canvasSize, templateElements, presentation, onExitPresentation, isMobile } = props;
30081
+ const {
30082
+ mode,
30083
+ slides,
30084
+ canvasSize,
30085
+ templateElements,
30086
+ presentation,
30087
+ onExitPresentation,
30088
+ isMobile,
30089
+ onUpdateNotes
30090
+ } = props;
29550
30091
  const presenterActive = mode === "present" && presentation.presenterMode;
29551
30092
  return /* @__PURE__ */ jsxs(Fragment, { children: [
30093
+ mode === "present" && !presentation.presenterMode && /* @__PURE__ */ jsx(PresentationAudienceEffects, { snapshot: presentation.presenterSnapshot }),
29552
30094
  presenterActive && (isMobile ? /* @__PURE__ */ jsx(
29553
30095
  MobilePresenterView,
29554
30096
  {
@@ -29572,7 +30114,20 @@ function ViewerPresentationLayer(props) {
29572
30114
  onExit: onExitPresentation,
29573
30115
  onOpenAudienceWindow: presentation.openAudienceWindow,
29574
30116
  onCloseAudienceWindow: presentation.closeAudienceWindow,
29575
- isAudienceWindowOpen: presentation.isAudienceWindowOpen()
30117
+ isAudienceWindowOpen: presentation.isAudienceWindowOpen(),
30118
+ snapshot: presentation.presenterSnapshot,
30119
+ onNavigateToSlide: presentation.navigateToSlide,
30120
+ onToggleTimer: presentation.togglePresenterTimer,
30121
+ onResetTimer: presentation.resetPresenterTimer,
30122
+ onStepZoom: presentation.stepPresenterZoom,
30123
+ onResetZoom: presentation.resetPresenterZoom,
30124
+ onSetBlackout: presentation.setPresenterBlackout,
30125
+ onUpdateSnapshot: presentation.updatePresenterSnapshot,
30126
+ onToggleSubtitles: () => presentation.setPresenterSubtitlesVisible(
30127
+ !presentation.presenterSnapshot.subtitlesVisible
30128
+ ),
30129
+ onSwapDisplays: () => void presentation.swapPresenterDisplays(),
30130
+ onUpdateNotes
29576
30131
  }
29577
30132
  )),
29578
30133
  mode === "present" && presentation.rehearsing && /* @__PURE__ */ jsx(
@@ -29750,7 +30305,7 @@ function ViewerCanvasArea(props) {
29750
30305
  {
29751
30306
  handoutMaster: s.handoutMaster,
29752
30307
  canvasSize,
29753
- slidesPerPage: s.handoutSlidesPerPage
30308
+ slidesPerPage: s.handoutMaster?.slidesPerPage ?? s.handoutSlidesPerPage
29754
30309
  }
29755
30310
  ) : /* @__PURE__ */ jsx(
29756
30311
  SlideCanvas,
@@ -30797,6 +31352,11 @@ function ThemeGallery({
30797
31352
  const { t } = useTranslation();
30798
31353
  const [selectedTheme, setSelectedTheme] = useState(currentTheme ?? null);
30799
31354
  const fileInputRef = useRef(null);
31355
+ useEffect(() => {
31356
+ if (open) {
31357
+ setSelectedTheme(currentTheme ?? null);
31358
+ }
31359
+ }, [currentTheme, open]);
30800
31360
  if (!open) {
30801
31361
  return null;
30802
31362
  }
@@ -30924,6 +31484,9 @@ function ViewerSidePanels(props) {
30924
31484
  onResizeRight
30925
31485
  } = props;
30926
31486
  const effectiveSlide = mode === "master" ? masterPseudoSlide : activeSlide;
31487
+ const currentBuiltInTheme = BUILT_IN_THEMES.find(
31488
+ (candidate) => themeColorSchemesEqual(s.theme?.colorScheme, candidate.colorScheme)
31489
+ ) ?? null;
30927
31490
  return /* @__PURE__ */ jsxs(Fragment, { children: [
30928
31491
  (mode === "edit" || mode === "master") && s.isInspectorPaneOpen && onResizeRight && /* @__PURE__ */ jsx(ResizeHandle, { direction: "horizontal", onResize: onResizeRight }),
30929
31492
  /* @__PURE__ */ jsx(
@@ -31021,7 +31584,7 @@ function ViewerSidePanels(props) {
31021
31584
  ThemeGallery,
31022
31585
  {
31023
31586
  open: s.isThemeGalleryOpen,
31024
- currentTheme: null,
31587
+ currentTheme: currentBuiltInTheme,
31025
31588
  canEdit,
31026
31589
  onClose: () => s.setIsThemeGalleryOpen(false),
31027
31590
  onApplyTheme: (theme) => {
@@ -31125,12 +31688,28 @@ function ViewerMainContent(props) {
31125
31688
  masterViewTab: state.masterViewTab,
31126
31689
  notesMaster: state.notesMaster,
31127
31690
  handoutMaster: state.handoutMaster,
31128
- handoutSlidesPerPage: state.handoutSlidesPerPage,
31691
+ handoutSlidesPerPage: state.handoutMaster?.slidesPerPage ?? state.handoutSlidesPerPage,
31129
31692
  onSelectMaster: dialogs.handleSelectMaster,
31130
31693
  onSelectLayout: dialogs.handleSelectLayout,
31131
- onCollapse: () => state.setIsSlidesPaneOpen(false),
31694
+ onCollapse: dialogs.handleCloseMasterView,
31132
31695
  onTabChange: state.setMasterViewTab,
31133
- onHandoutSlidesPerPageChange: state.setHandoutSlidesPerPage
31696
+ onHandoutSlidesPerPageChange: (count) => {
31697
+ state.setHandoutSlidesPerPage(count);
31698
+ state.setHandoutMaster(
31699
+ (master) => master ? { ...master, slidesPerPage: count } : master
31700
+ );
31701
+ state.setIsDirty(true);
31702
+ },
31703
+ onNotesMasterBackgroundChange: (backgroundColor) => {
31704
+ state.setNotesMaster((master) => master ? { ...master, backgroundColor } : master);
31705
+ state.setIsDirty(true);
31706
+ },
31707
+ onHandoutMasterBackgroundChange: (backgroundColor) => {
31708
+ state.setHandoutMaster(
31709
+ (master) => master ? { ...master, backgroundColor } : master
31710
+ );
31711
+ state.setIsDirty(true);
31712
+ }
31134
31713
  }
31135
31714
  ),
31136
31715
  /* @__PURE__ */ jsx(
@@ -31862,6 +32441,7 @@ var PowerPointViewer = forwardRef(
31862
32441
  {
31863
32442
  style: themeStyle,
31864
32443
  "data-pptx-viewer": "",
32444
+ "aria-busy": loading,
31865
32445
  className: "h-full w-full bg-background text-foreground relative",
31866
32446
  children: [
31867
32447
  /* @__PURE__ */ jsx(
@@ -32088,6 +32668,7 @@ var PowerPointViewer = forwardRef(
32088
32668
  templateElements: state.templateElements,
32089
32669
  presentation,
32090
32670
  onExitPresentation: () => handleSetMode("edit"),
32671
+ onUpdateNotes: propertyHandlers.handleUpdateNotes,
32091
32672
  isMobile
32092
32673
  }
32093
32674
  )