pptx-react-viewer 1.17.0 → 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 +16 -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-XW5OMXW6.js → chunk-73ST7AC6.js} +2269 -1651
  16. package/dist/chunk-73ST7AC6.js.br +0 -0
  17. package/dist/chunk-73ST7AC6.js.gz +0 -0
  18. package/dist/{chunk-WGLJ32FW.js → chunk-AALZCSY4.js} +8 -1
  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-3P6O7ZDM.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-LNSHC2Z5.mjs → chunk-KMLIWCNL.mjs} +1460 -842
  34. package/dist/chunk-KMLIWCNL.mjs.br +0 -0
  35. package/dist/chunk-KMLIWCNL.mjs.gz +0 -0
  36. package/dist/{chunk-HJQZJ3VE.mjs → chunk-RIRK64DM.mjs} +8 -1
  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-LLHFNGAF.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 +3 -1
  71. package/dist/index.js.gz +0 -0
  72. package/dist/index.mjs +5 -5
  73. package/dist/index.mjs.br +3 -4
  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-3P6O7ZDM.js.br +0 -0
  96. package/dist/chunk-3P6O7ZDM.js.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-HJQZJ3VE.mjs.br +0 -0
  100. package/dist/chunk-HJQZJ3VE.mjs.gz +0 -0
  101. package/dist/chunk-JWS3RQKN.js.br +0 -0
  102. package/dist/chunk-JWS3RQKN.js.gz +0 -0
  103. package/dist/chunk-LLHFNGAF.mjs.br +0 -0
  104. package/dist/chunk-LLHFNGAF.mjs.gz +0 -0
  105. package/dist/chunk-LNSHC2Z5.mjs.br +0 -0
  106. package/dist/chunk-LNSHC2Z5.mjs.gz +0 -0
  107. package/dist/chunk-MGLRKGCH.js.br +0 -0
  108. package/dist/chunk-MGLRKGCH.js.gz +0 -0
  109. package/dist/chunk-WGLJ32FW.js.br +0 -0
  110. package/dist/chunk-WGLJ32FW.js.gz +0 -0
  111. package/dist/chunk-XRYREYXC.mjs.br +0 -0
  112. package/dist/chunk-XRYREYXC.mjs.gz +0 -0
  113. package/dist/chunk-XW5OMXW6.js.br +0 -0
  114. package/dist/chunk-XW5OMXW6.js.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-LLHFNGAF.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-HJQZJ3VE.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);
@@ -243,6 +243,7 @@ function CollaborationStatusIndicator({
243
243
  return /* @__PURE__ */ jsxs(
244
244
  "div",
245
245
  {
246
+ role: "status",
246
247
  "data-testid": "collaboration-status",
247
248
  className: "flex items-center gap-1.5",
248
249
  "aria-label": t("pptx.collaboration.statusAriaLabel", {
@@ -465,12 +466,21 @@ function LoadingState({ className }) {
465
466
  return /* @__PURE__ */ jsx(
466
467
  "div",
467
468
  {
469
+ role: "status",
470
+ "aria-live": "polite",
471
+ "aria-atomic": "true",
468
472
  className: cn(
469
473
  "h-full w-full flex items-center justify-center text-muted-foreground",
470
474
  className
471
475
  ),
472
476
  children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
473
- /* @__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
+ ),
474
484
  /* @__PURE__ */ jsx("span", { children: t("pptx.viewer.loading") })
475
485
  ] })
476
486
  }
@@ -480,6 +490,8 @@ function ErrorState({ className, error }) {
480
490
  return /* @__PURE__ */ jsx(
481
491
  "div",
482
492
  {
493
+ role: "alert",
494
+ "aria-live": "assertive",
483
495
  className: cn(
484
496
  "h-full w-full flex items-center justify-center p-6 text-center text-red-400",
485
497
  className
@@ -883,6 +895,17 @@ function ResizeHandle({
883
895
  }
884
896
  );
885
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
+ }
886
909
  function DrawingShapeRenderer({
887
910
  elementId,
888
911
  shapes,
@@ -2665,7 +2688,7 @@ function SlideThumbnailImpl({
2665
2688
  "div",
2666
2689
  {
2667
2690
  className: "relative w-full overflow-hidden rounded border border-border bg-white",
2668
- style: { height: previewHeight, ...getSlideBackgroundStyle(slide) },
2691
+ style: { height: previewHeight, ...getReactSlideBackgroundStyle(slide) },
2669
2692
  children: /* @__PURE__ */ jsxs(
2670
2693
  "div",
2671
2694
  {
@@ -4834,7 +4857,7 @@ function FailedToLoad() {
4834
4857
  var LazyModel3DScene = React5.lazy(
4835
4858
  async () => {
4836
4859
  try {
4837
- return await import('./Model3DScene-QHDYQXL2.mjs');
4860
+ return await import('./Model3DScene-JGVLIDLD.mjs');
4838
4861
  } catch {
4839
4862
  return { default: FailedToLoad };
4840
4863
  }
@@ -5809,7 +5832,7 @@ var ElementRenderer = React5.memo(
5809
5832
  const cur = effectiveIsInlineEditing ? "cursor-text" : effectiveCanInteract ? elementLocks?.noMove ? "cursor-default" : "cursor-move" : hasAction || isZoom ? "cursor-pointer" : "";
5810
5833
  const isPresentationPassive = !effectiveCanInteract;
5811
5834
  const isFullscreenMedia = el.type === "media" && Boolean(el.fullScreen) && isPresentationPassive && isMediaPlaying;
5812
- const ariaRole = getAriaRole(el);
5835
+ const ariaRole = isActionable ? "button" : getAriaRole(el);
5813
5836
  const ariaLabel = getAriaLabel(el);
5814
5837
  const ariaRoleDescription = getAriaRoleDescription(el);
5815
5838
  const isFocusable = effectiveCanInteract || isActionable;
@@ -5853,6 +5876,10 @@ var ElementRenderer = React5.memo(
5853
5876
  e.preventDefault();
5854
5877
  e.stopPropagation();
5855
5878
  onInlineEditCancel();
5879
+ } else if ((e.key === "Enter" || e.key === " ") && isActionable) {
5880
+ e.preventDefault();
5881
+ e.stopPropagation();
5882
+ e.currentTarget.click();
5856
5883
  }
5857
5884
  },
5858
5885
  onClick: (e) => {
@@ -6445,11 +6472,14 @@ function SlideItemInner({
6445
6472
  const scale = SLIDE_NAV_THUMBNAIL_WIDTH / safeCanvasWidth;
6446
6473
  const previewHeight = Math.max(56, Math.round(safeCanvasHeight * scale));
6447
6474
  return /* @__PURE__ */ jsxs(
6448
- "div",
6475
+ "button",
6449
6476
  {
6477
+ type: "button",
6450
6478
  ref: slideRef,
6479
+ "aria-label": `Go to slide ${slideIndex + 1}`,
6480
+ "aria-current": isActive ? "true" : void 0,
6451
6481
  className: cn(
6452
- "group relative flex items-center gap-1 cursor-pointer py-0.5 px-1 transition-all",
6482
+ "group relative flex w-full items-center gap-1 cursor-pointer border-0 bg-transparent py-0.5 px-1 text-left transition-all",
6453
6483
  isActive && "bg-accent/40 before:absolute before:left-0 before:top-1 before:bottom-1 before:w-[3px] before:bg-primary before:rounded-r",
6454
6484
  isHidden && "opacity-50"
6455
6485
  ),
@@ -6892,7 +6922,8 @@ var HANDOUT_SLIDES_PER_PAGE_OPTIONS = [1, 2, 3, 4, 6, 9];
6892
6922
  function HandoutMasterPanel({
6893
6923
  handoutMaster,
6894
6924
  slidesPerPage,
6895
- onSlidesPerPageChange
6925
+ onSlidesPerPageChange,
6926
+ onBackgroundChange
6896
6927
  }) {
6897
6928
  const { t } = useTranslation();
6898
6929
  if (!handoutMaster) {
@@ -6905,6 +6936,7 @@ function HandoutMasterPanel({
6905
6936
  "button",
6906
6937
  {
6907
6938
  type: "button",
6939
+ "aria-pressed": slidesPerPage === count,
6908
6940
  className: cn(
6909
6941
  "px-2 py-1.5 rounded text-[11px] font-medium transition-colors",
6910
6942
  slidesPerPage === count ? "bg-primary text-white" : "bg-accent/50 text-muted-foreground hover:bg-accent/70 hover:text-foreground"
@@ -6918,12 +6950,13 @@ function HandoutMasterPanel({
6918
6950
  /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-border/60 bg-muted/40 p-2", children: [
6919
6951
  /* @__PURE__ */ jsx("div", { className: "text-[10px] text-muted-foreground mb-1", children: t("pptx.master.handoutBackground") }),
6920
6952
  /* @__PURE__ */ jsx(
6921
- "div",
6953
+ "input",
6922
6954
  {
6955
+ type: "color",
6956
+ "aria-label": "Master background color",
6923
6957
  className: "w-full h-8 rounded border border-border",
6924
- style: {
6925
- backgroundColor: handoutMaster.backgroundColor ?? "#ffffff"
6926
- }
6958
+ value: handoutMaster.backgroundColor ?? "#ffffff",
6959
+ onChange: (event) => onBackgroundChange(event.target.value)
6927
6960
  }
6928
6961
  )
6929
6962
  ] }),
@@ -6943,7 +6976,10 @@ function HandoutMasterPanel({
6943
6976
  ] })
6944
6977
  ] });
6945
6978
  }
6946
- function NotesMasterPanel({ notesMaster }) {
6979
+ function NotesMasterPanel({
6980
+ notesMaster,
6981
+ onBackgroundChange
6982
+ }) {
6947
6983
  const { t } = useTranslation();
6948
6984
  const placeholderLabels = useMemo(() => {
6949
6985
  if (!notesMaster?.placeholders) {
@@ -6961,10 +6997,13 @@ function NotesMasterPanel({ notesMaster }) {
6961
6997
  /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-border/60 bg-muted/40 p-2", children: [
6962
6998
  /* @__PURE__ */ jsx("div", { className: "text-[10px] text-muted-foreground mb-1", children: t("pptx.master.notesMasterBackground") }),
6963
6999
  /* @__PURE__ */ jsx(
6964
- "div",
7000
+ "input",
6965
7001
  {
7002
+ type: "color",
7003
+ "aria-label": "Master background color",
6966
7004
  className: "w-full h-8 rounded border border-border",
6967
- style: { backgroundColor: notesMaster.backgroundColor ?? "#ffffff" }
7005
+ value: notesMaster.backgroundColor ?? "#ffffff",
7006
+ onChange: (event) => onBackgroundChange(event.target.value)
6968
7007
  }
6969
7008
  )
6970
7009
  ] }),
@@ -7111,7 +7150,9 @@ function MasterViewSidebar({
7111
7150
  onSelectLayout,
7112
7151
  onCollapse,
7113
7152
  onTabChange,
7114
- onHandoutSlidesPerPageChange
7153
+ onHandoutSlidesPerPageChange,
7154
+ onNotesMasterBackgroundChange,
7155
+ onHandoutMasterBackgroundChange
7115
7156
  }) {
7116
7157
  const scrollRef = useRef(null);
7117
7158
  const { t } = useTranslation();
@@ -7127,56 +7168,80 @@ function MasterViewSidebar({
7127
7168
  },
7128
7169
  [onSelectLayout]
7129
7170
  );
7130
- return /* @__PURE__ */ jsxs("aside", { className: "flex h-full flex-col border-r border-border/80 bg-background/70 backdrop-blur-sm w-56", children: [
7131
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-2", children: [
7132
- /* @__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") }),
7133
- /* @__PURE__ */ jsx(
7134
- "button",
7135
- {
7136
- type: "button",
7137
- className: "rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
7138
- title: t("pptx.master.collapseMasterPane"),
7139
- onClick: onCollapse,
7140
- children: /* @__PURE__ */ jsx(LuPanelLeftClose, { className: "h-3.5 w-3.5" })
7141
- }
7142
- )
7143
- ] }),
7144
- /* @__PURE__ */ jsx("div", { className: "flex border-b border-border/60 px-1", children: TABS.map((tab) => /* @__PURE__ */ jsx(
7145
- "button",
7146
- {
7147
- type: "button",
7148
- className: cn(
7149
- "flex-1 px-1 py-1.5 text-[10px] font-medium transition-colors border-b-2",
7150
- 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
+ }
7151
7212
  ),
7152
- onClick: () => onTabChange(tab.key),
7153
- children: t(tab.labelKey)
7154
- },
7155
- tab.key
7156
- )) }),
7157
- /* @__PURE__ */ jsxs("div", { ref: scrollRef, className: "flex-1 space-y-2 overflow-y-auto px-1.5 pb-2 pt-1", children: [
7158
- masterViewTab === "slides" && /* @__PURE__ */ jsx(
7159
- SlideMastersList,
7160
- {
7161
- slideMasters,
7162
- activeMasterIndex,
7163
- activeLayoutIndex,
7164
- canvasSize,
7165
- onSelectMaster: handleMasterClick,
7166
- onSelectLayout: handleLayoutClick
7167
- }
7168
- ),
7169
- masterViewTab === "notes" && /* @__PURE__ */ jsx(NotesMasterPanel, { notesMaster }),
7170
- masterViewTab === "handout" && /* @__PURE__ */ jsx(
7171
- HandoutMasterPanel,
7172
- {
7173
- handoutMaster,
7174
- slidesPerPage: handoutSlidesPerPage,
7175
- onSlidesPerPageChange: onHandoutSlidesPerPageChange
7176
- }
7177
- )
7178
- ] })
7179
- ] });
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
+ );
7180
7245
  }
7181
7246
  var DEFAULT_NOTES_WIDTH = 720;
7182
7247
  var DEFAULT_NOTES_HEIGHT = 960;
@@ -7196,6 +7261,12 @@ var DEFAULT_POSITIONS = {
7196
7261
  dt: { x: 0.6, y: 0, w: 0.4, h: 0.04 },
7197
7262
  sldNum: { x: 0.6, y: 0.96, w: 0.4, h: 0.04 }
7198
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
+ }
7199
7270
  function NotesMasterCanvas({
7200
7271
  notesMaster,
7201
7272
  canvasSize,
@@ -7339,7 +7410,24 @@ function NotesMasterCanvas({
7339
7410
  },
7340
7411
  `${ph.type}-${ph.idx ?? "default"}`
7341
7412
  );
7342
- })
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
+ ))
7343
7431
  ]
7344
7432
  }
7345
7433
  ) });
@@ -7347,6 +7435,12 @@ function NotesMasterCanvas({
7347
7435
  var PAGE_WIDTH = 720;
7348
7436
  var PAGE_HEIGHT = 960;
7349
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
+ }
7350
7444
  function computeSlotLayout(slidesPerPage, slideAspect) {
7351
7445
  const mx = MARGIN;
7352
7446
  const my = MARGIN;
@@ -7498,6 +7592,23 @@ function HandoutMasterCanvas({
7498
7592
  i
7499
7593
  );
7500
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
+ )),
7501
7612
  /* @__PURE__ */ jsx(
7502
7613
  "div",
7503
7614
  {
@@ -9938,6 +10049,18 @@ function ComparePanel({
9938
10049
  )) })
9939
10050
  ] });
9940
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
+ }
9941
10064
  function getPasswordStrength(password) {
9942
10065
  if (!password) {
9943
10066
  return 0;
@@ -9979,6 +10102,8 @@ function PasswordProtectionDialog({
9979
10102
  const [confirmPassword, setConfirmPassword] = useState("");
9980
10103
  const [showPassword, setShowPassword] = useState(false);
9981
10104
  const [error, setError] = useState("");
10105
+ const dialogRef = useRef(null);
10106
+ const passwordRef = useRef(null);
9982
10107
  const strength = useMemo(() => getPasswordStrength(password), [password]);
9983
10108
  const strengthLabel = useMemo(() => {
9984
10109
  const labels = [
@@ -10021,6 +10146,7 @@ function PasswordProtectionDialog({
10021
10146
  setError("");
10022
10147
  onClose();
10023
10148
  }, [onClose]);
10149
+ useModalFocus(isOpen, dialogRef, handleClose, passwordRef);
10024
10150
  if (!isOpen) {
10025
10151
  return null;
10026
10152
  }
@@ -10040,117 +10166,148 @@ function PasswordProtectionDialog({
10040
10166
  {
10041
10167
  style: { zIndex: 1201 },
10042
10168
  className: "fixed inset-0 flex items-center justify-center pointer-events-none",
10043
- 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: [
10044
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 py-4 border-b border-border/60", children: [
10045
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
10046
- /* @__PURE__ */ jsx(LuLock, { className: "w-5 h-5 text-primary" }),
10047
- /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-foreground", children: t("pptx.security.protectPresentation") })
10048
- ] }),
10049
- /* @__PURE__ */ jsx(
10050
- "button",
10051
- {
10052
- type: "button",
10053
- onClick: handleClose,
10054
- className: "p-1 rounded hover:bg-accent transition-colors",
10055
- "aria-label": t("common.close"),
10056
- children: /* @__PURE__ */ jsx(LuX, { className: "w-4 h-4 text-muted-foreground" })
10057
- }
10058
- )
10059
- ] }),
10060
- /* @__PURE__ */ jsxs("div", { className: "px-5 py-4 space-y-4", children: [
10061
- 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: [
10062
- /* @__PURE__ */ jsx(LuShieldCheck, { className: "w-4 h-4 text-green-400 shrink-0" }),
10063
- /* @__PURE__ */ jsx("span", { className: "text-xs text-green-300", children: t("pptx.security.currentlyProtected") })
10064
- ] }),
10065
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("pptx.security.description") }),
10066
- /* @__PURE__ */ jsxs("div", { children: [
10067
- /* @__PURE__ */ jsx("label", { className: "block text-xs text-foreground mb-1", children: t("pptx.security.password") }),
10068
- /* @__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
+ ] }),
10069
10184
  /* @__PURE__ */ jsx(
10070
- "input",
10185
+ "button",
10071
10186
  {
10072
- type: showPassword ? "text" : "password",
10073
- 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",
10074
- placeholder: t("pptx.security.passwordPlaceholder"),
10075
- value: password,
10076
- onChange: (e) => {
10077
- setPassword(e.target.value);
10078
- setError("");
10079
- }
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" })
10080
10192
  }
10081
- ),
10082
- /* @__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(
10083
10279
  "button",
10084
10280
  {
10085
10281
  type: "button",
10086
- onClick: () => setShowPassword(!showPassword),
10087
- className: "absolute right-2 top-1/2 -translate-y-1/2 p-1 text-muted-foreground hover:text-foreground",
10088
- "aria-label": showPassword ? t("pptx.security.hidePassword") : t("pptx.security.showPassword"),
10089
- 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")
10090
10285
  }
10091
- )
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
+ ] })
10092
10307
  ] })
10093
- ] }),
10094
- password && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
10095
- /* @__PURE__ */ jsx("div", { className: "flex gap-1", children: Array.from({ length: 5 }).map((_, i) => /* @__PURE__ */ jsx(
10096
- "div",
10097
- {
10098
- className: `h-1 flex-1 rounded-full transition-colors ${i <= strength ? STRENGTH_COLORS[strength] : "bg-accent"}`
10099
- },
10100
- `str-${i}`
10101
- )) }),
10102
- /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: strengthLabel })
10103
- ] }),
10104
- /* @__PURE__ */ jsxs("div", { children: [
10105
- /* @__PURE__ */ jsx("label", { className: "block text-xs text-foreground mb-1", children: t("pptx.security.confirmPassword") }),
10106
- /* @__PURE__ */ jsx(
10107
- "input",
10108
- {
10109
- type: showPassword ? "text" : "password",
10110
- 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",
10111
- placeholder: t("pptx.security.confirmPasswordPlaceholder"),
10112
- value: confirmPassword,
10113
- onChange: (e) => {
10114
- setConfirmPassword(e.target.value);
10115
- setError("");
10116
- }
10117
- }
10118
- )
10119
- ] }),
10120
- error && /* @__PURE__ */ jsx("p", { className: "text-xs text-red-400", children: error })
10121
- ] }),
10122
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 py-3 border-t border-border/60", children: [
10123
- /* @__PURE__ */ jsx("div", { children: isCurrentlyProtected && /* @__PURE__ */ jsx(
10124
- "button",
10125
- {
10126
- type: "button",
10127
- onClick: handleRemove,
10128
- className: "text-xs text-red-400 hover:text-red-300 transition-colors",
10129
- children: t("pptx.security.removePassword")
10130
- }
10131
- ) }),
10132
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
10133
- /* @__PURE__ */ jsx(
10134
- "button",
10135
- {
10136
- type: "button",
10137
- onClick: handleClose,
10138
- className: "px-3 py-1.5 text-xs rounded-lg border border-border text-foreground hover:bg-muted transition-colors",
10139
- children: t("common.cancel")
10140
- }
10141
- ),
10142
- /* @__PURE__ */ jsx(
10143
- "button",
10144
- {
10145
- type: "button",
10146
- onClick: handleSubmit,
10147
- className: "px-3 py-1.5 text-xs rounded-lg bg-primary text-white hover:bg-primary/80 transition-colors",
10148
- children: isCurrentlyProtected ? t("pptx.security.updatePassword") : t("pptx.security.setPassword")
10149
- }
10150
- )
10151
- ] })
10152
- ] })
10153
- ] })
10308
+ ]
10309
+ }
10310
+ )
10154
10311
  }
10155
10312
  )
10156
10313
  ] });
@@ -10982,17 +11139,224 @@ function EncryptedFileDialog({
10982
11139
  )
10983
11140
  ] });
10984
11141
  }
10985
- function renderNotesSegments(segments) {
10986
- return notesSegmentsToSpans(segments).map((span) => {
10987
- if (span.kind === "break") {
10988
- return React5.createElement("br", { key: span.key });
10989
- }
10990
- 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(
10991
11174
  "span",
10992
- { key: span.key, style: span.style },
10993
- span.text
10994
- );
10995
- });
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
+ ] });
10996
11360
  }
10997
11361
  function ScaledSlidePreviewImpl({
10998
11362
  slide,
@@ -11284,6 +11648,250 @@ function arePropsEqual4(prev, next) {
11284
11648
  return true;
11285
11649
  }
11286
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
+ }
11287
11895
  function PresenterView({
11288
11896
  slides,
11289
11897
  currentSlideIndex,
@@ -11294,187 +11902,126 @@ function PresenterView({
11294
11902
  onExit,
11295
11903
  onOpenAudienceWindow,
11296
11904
  onCloseAudienceWindow,
11297
- 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
11298
11917
  }) {
11299
11918
  const { t } = useTranslation();
11300
- const [mounted, setMounted] = useState(false);
11301
- useEffect(() => {
11302
- const timer = window.setTimeout(() => setMounted(true), 300);
11303
- return () => window.clearTimeout(timer);
11304
- }, []);
11305
11919
  const [now, setNow] = useState(Date.now());
11306
11920
  useEffect(() => {
11307
11921
  const interval = window.setInterval(() => setNow(Date.now()), 1e3);
11308
11922
  return () => window.clearInterval(interval);
11309
11923
  }, []);
11310
- const elapsed = presentationStartTime ? now - presentationStartTime : 0;
11311
- const [notesFontSize, setNotesFontSize] = useState(NOTES_FONT_SIZE_DEFAULT);
11312
- const increaseNotesFontSize = useCallback(() => {
11313
- setNotesFontSize((prev) => clampNotesFontSize(prev + NOTES_FONT_SIZE_STEP));
11314
- }, []);
11315
- const decreaseNotesFontSize = useCallback(() => {
11316
- setNotesFontSize((prev) => clampNotesFontSize(prev - NOTES_FONT_SIZE_STEP));
11317
- }, []);
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
+ );
11318
11931
  const currentSlide = slides[currentSlideIndex];
11319
- const nextSlide = currentSlideIndex + 1 < slides.length ? slides[currentSlideIndex + 1] : void 0;
11320
- const notesText = currentSlide?.notes ?? "";
11321
- const notesSegments = currentSlide?.notesSegments;
11322
- const hasRichNotes = notesSegments && notesSegments.length > 0;
11323
11932
  if (!currentSlide) {
11324
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") });
11325
11934
  }
11326
11935
  const TIMER_SEGMENT_MS = 5 * 60 * 1e3;
11327
11936
  const timerProgress = Math.min(100, elapsed % TIMER_SEGMENT_MS / TIMER_SEGMENT_MS * 100);
11328
11937
  const timerSegment = Math.floor(elapsed / TIMER_SEGMENT_MS);
11329
- return /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 z-50 flex flex-col bg-card text-foreground", children: [
11330
- /* @__PURE__ */ jsxs("div", { className: "flex flex-1 min-h-0", children: [
11331
- /* @__PURE__ */ jsxs("div", { className: "flex-[7] flex flex-col items-center justify-center bg-black p-6 min-w-0", children: [
11332
- /* @__PURE__ */ jsx(
11333
- ScaledSlidePreview,
11334
- {
11335
- slide: currentSlide,
11336
- templateElements,
11337
- canvasSize
11338
- }
11339
- ),
11340
- /* @__PURE__ */ jsx("div", { className: "mt-3 text-xs font-mono tabular-nums text-white/50 select-none", children: t("pptx.presenter.slideLabel", {
11341
- current: currentSlideIndex + 1,
11342
- total: slides.length,
11343
- defaultValue: `Slide ${currentSlideIndex + 1} of ${slides.length}`
11344
- }) })
11345
- ] }),
11346
- /* @__PURE__ */ jsxs("div", { className: "flex-[3] flex flex-col bg-background border-l border-border min-w-[260px] max-w-[440px]", children: [
11347
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-border/60", children: [
11348
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
11349
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground uppercase tracking-wider", children: t("pptx.presenter.currentTime") }),
11350
- /* @__PURE__ */ jsx("span", { className: "text-lg font-mono tabular-nums text-foreground", children: formatTime(new Date(now)) })
11351
- ] }),
11352
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-end", children: [
11353
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground uppercase tracking-wider", children: t("pptx.presenter.elapsed") }),
11354
- /* @__PURE__ */ jsx("span", { className: "text-lg font-mono tabular-nums text-primary", children: formatElapsed(elapsed) })
11355
- ] }),
11356
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
11357
- onOpenAudienceWindow && mounted && /* @__PURE__ */ jsx(
11358
- "button",
11359
- {
11360
- type: "button",
11361
- onClick: () => {
11362
- if (isAudienceWindowOpen) {
11363
- onCloseAudienceWindow?.();
11364
- } else {
11365
- onOpenAudienceWindow();
11366
- }
11367
- },
11368
- className: "p-1.5 rounded hover:bg-accent text-muted-foreground hover:text-foreground transition-colors",
11369
- title: isAudienceWindowOpen ? t("pptx.presenter.closeAudienceWindow") : t("pptx.presenter.openAudienceWindow"),
11370
- "aria-label": isAudienceWindowOpen ? t("pptx.presenter.closeAudienceWindow") : t("pptx.presenter.openAudienceWindow"),
11371
- children: isAudienceWindowOpen ? /* @__PURE__ */ jsx(LuMonitorOff, { className: "w-5 h-5" }) : /* @__PURE__ */ jsx(LuMonitor, { className: "w-5 h-5" })
11372
- }
11373
- ),
11374
- /* @__PURE__ */ jsx(
11375
- "button",
11376
- {
11377
- type: "button",
11378
- onClick: onExit,
11379
- className: "p-1.5 rounded hover:bg-accent text-muted-foreground hover:text-foreground transition-colors",
11380
- title: t("pptx.presenter.endPresentation"),
11381
- "aria-label": t("pptx.presenter.endPresentation"),
11382
- children: /* @__PURE__ */ jsx(LuX, { className: "w-5 h-5" })
11383
- }
11384
- )
11385
- ] })
11386
- ] }),
11387
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-2 border-b border-border/60", children: [
11388
- /* @__PURE__ */ jsxs(
11389
- "button",
11390
- {
11391
- type: "button",
11392
- onClick: () => onMovePresentationSlide(-1),
11393
- disabled: currentSlideIndex === 0,
11394
- 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",
11395
- title: t("pptx.presenter.previousSlide"),
11396
- children: [
11397
- /* @__PURE__ */ jsx(LuChevronLeft, { className: "w-4 h-4" }),
11398
- t("pptx.presenter.prev")
11399
- ]
11400
- }
11401
- ),
11402
- /* @__PURE__ */ jsxs("span", { className: "text-sm font-mono tabular-nums text-foreground", children: [
11403
- currentSlideIndex + 1,
11404
- " / ",
11405
- slides.length
11406
- ] }),
11407
- /* @__PURE__ */ jsxs(
11408
- "button",
11409
- {
11410
- type: "button",
11411
- onClick: () => onMovePresentationSlide(1),
11412
- disabled: currentSlideIndex >= slides.length - 1,
11413
- 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",
11414
- title: t("pptx.presenter.nextSlide"),
11415
- children: [
11416
- t("pptx.presenter.next"),
11417
- /* @__PURE__ */ jsx(LuChevronRight, { className: "w-4 h-4" })
11418
- ]
11419
- }
11420
- )
11421
- ] }),
11422
- /* @__PURE__ */ jsxs("div", { className: "px-4 py-3 border-b border-border/60", children: [
11423
- /* @__PURE__ */ jsx("div", { className: "text-[10px] text-muted-foreground uppercase tracking-wider mb-2", children: t("pptx.presenter.nextSlidePreview") }),
11424
- nextSlide ? /* @__PURE__ */ jsx(
11425
- ScaledSlidePreview,
11426
- {
11427
- slide: nextSlide,
11428
- templateElements,
11429
- canvasSize
11430
- }
11431
- ) : /* @__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") })
11432
- ] }),
11433
- /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col min-h-0 px-4 py-3", children: [
11434
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
11435
- /* @__PURE__ */ jsx("div", { className: "text-[10px] text-muted-foreground uppercase tracking-wider", children: t("pptx.presenter.speakerNotes") }),
11436
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", 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
+ ),
11959
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-1 min-h-0", children: [
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",
11963
+ {
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: [
11437
11972
  /* @__PURE__ */ jsx(
11438
- "button",
11973
+ ScaledSlidePreview,
11439
11974
  {
11440
- type: "button",
11441
- onClick: decreaseNotesFontSize,
11442
- disabled: notesFontSize <= NOTES_FONT_SIZE_MIN,
11443
- className: "p-0.5 rounded hover:bg-accent text-muted-foreground hover:text-foreground disabled:opacity-30 disabled:cursor-not-allowed transition-colors",
11444
- title: t("pptx.presenter.decreaseFontSize"),
11445
- "aria-label": t("pptx.presenter.decreaseFontSize"),
11446
- children: /* @__PURE__ */ jsx(LuMinus, { className: "w-3.5 h-3.5" })
11975
+ slide: currentSlide,
11976
+ templateElements,
11977
+ canvasSize
11447
11978
  }
11448
11979
  ),
11449
- /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-mono tabular-nums text-muted-foreground min-w-[28px] text-center select-none", children: [
11450
- notesFontSize,
11451
- "px"
11452
- ] }),
11453
- /* @__PURE__ */ jsx(
11454
- "button",
11455
- {
11456
- type: "button",
11457
- onClick: increaseNotesFontSize,
11458
- disabled: notesFontSize >= NOTES_FONT_SIZE_MAX,
11459
- className: "p-0.5 rounded hover:bg-accent text-muted-foreground hover:text-foreground disabled:opacity-30 disabled:cursor-not-allowed transition-colors",
11460
- title: t("pptx.presenter.increaseFontSize"),
11461
- "aria-label": t("pptx.presenter.increaseFontSize"),
11462
- children: /* @__PURE__ */ jsx(LuPlus, { className: "w-3.5 h-3.5" })
11463
- }
11464
- )
11465
- ] })
11466
- ] }),
11467
- /* @__PURE__ */ jsx(
11468
- "div",
11469
- {
11470
- 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",
11471
- style: { fontSize: `${notesFontSize}px` },
11472
- children: hasRichNotes ? renderNotesSegments(notesSegments) : notesText.trim().length > 0 ? notesText : /* @__PURE__ */ jsx("span", { className: "italic text-muted-foreground", children: t("pptx.presenter.noNotes") })
11473
- }
11474
- )
11475
- ] })
11476
- ] })
11980
+ /* @__PURE__ */ jsx(PresentationAudienceEffects, { snapshot })
11981
+ ]
11982
+ }
11983
+ ),
11984
+ /* @__PURE__ */ jsx("div", { className: "mt-3 text-xs font-mono tabular-nums text-white/50 select-none", children: t("pptx.presenter.slideLabel", {
11985
+ current: currentSlideIndex + 1,
11986
+ total: slides.length,
11987
+ defaultValue: `Slide ${currentSlideIndex + 1} of ${slides.length}`
11988
+ }) })
11989
+ ] }),
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
+ )
11477
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
+ ),
11478
12025
  /* @__PURE__ */ jsx(
11479
12026
  "div",
11480
12027
  {
@@ -14611,23 +15158,12 @@ function MobileSheet({
14611
15158
  }) {
14612
15159
  const sheetRef = useRef(null);
14613
15160
  const { dragY, dragging, handlers } = useSheetDismissDrag(onClose);
14614
- useEffect(() => {
14615
- if (!open) {
14616
- return;
14617
- }
14618
- const handleKey = (e) => {
14619
- if (e.key === "Escape") {
14620
- onClose();
14621
- }
14622
- };
14623
- window.addEventListener("keydown", handleKey);
14624
- return () => window.removeEventListener("keydown", handleKey);
14625
- }, [open, onClose]);
15161
+ useModalFocus(open, sheetRef, onClose);
14626
15162
  if (!open) {
14627
15163
  return null;
14628
15164
  }
14629
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` };
14630
- 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: [
14631
15167
  /* @__PURE__ */ jsx(
14632
15168
  "button",
14633
15169
  {
@@ -14641,6 +15177,10 @@ function MobileSheet({
14641
15177
  "div",
14642
15178
  {
14643
15179
  ref: sheetRef,
15180
+ role: "dialog",
15181
+ "aria-modal": "true",
15182
+ "aria-label": typeof title === "string" ? title : "Mobile panel",
15183
+ tabIndex: -1,
14644
15184
  className: cn(
14645
15185
  "relative bg-background border-t border-border rounded-t-2xl shadow-2xl flex flex-col overflow-hidden",
14646
15186
  "animate-in slide-in-from-bottom duration-200",
@@ -14664,7 +15204,18 @@ function MobileSheet({
14664
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" }) }),
14665
15205
  (title || headerRight) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-4 pb-2 border-b border-border/60", children: [
14666
15206
  /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold text-foreground truncate", children: title }),
14667
- 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
+ )
14668
15219
  ] })
14669
15220
  ]
14670
15221
  }
@@ -15874,45 +16425,54 @@ function Toolbar(p) {
15874
16425
  className: "relative z-20 border-b border-border bg-secondary/50 overflow-visible",
15875
16426
  children: [
15876
16427
  /* @__PURE__ */ jsx(ToolbarPrimaryRow, { ...p }),
15877
- showRibbon && /* @__PURE__ */ jsxs("div", { className: "flex items-center border-b border-border/60 px-1 max-md:overflow-x-auto max-md:scrollbar-none", children: [
15878
- TOOLBAR_SECTIONS.map((s) => /* @__PURE__ */ jsx(
15879
- "button",
15880
- {
15881
- type: "button",
15882
- onClick: () => onSetToolbarSection(s.id),
15883
- className: cn(
15884
- "relative px-3.5 py-2 text-[12px] font-medium whitespace-nowrap transition-colors max-md:min-h-[36px] max-md:px-3",
15885
- toolbarSection === s.id ? s.id === "file" ? "text-white bg-primary/80 rounded-sm" : "text-foreground after:absolute after:-bottom-px after:left-0 after:right-0 after:h-[2.5px] after:bg-primary" : s.id === "file" ? "text-primary hover:bg-primary/15 rounded-sm" : "text-muted-foreground hover:text-foreground hover:bg-accent/30"
15886
- ),
15887
- children: t(s.labelKey)
15888
- },
15889
- s.id
15890
- )),
15891
- /* @__PURE__ */ jsx("div", { className: "flex-1" }),
15892
- /* @__PURE__ */ jsx(
15893
- TabRowActions,
15894
- {
15895
- onEnterRehearsalMode: p.canEdit ? p.onEnterRehearsalMode : void 0,
15896
- onOpenShareDialog: p.onOpenShareDialog,
15897
- onPackageForSharing: p.onPackageForSharing
15898
- }
15899
- ),
15900
- isNarrowViewport && /* @__PURE__ */ jsx(
15901
- "button",
15902
- {
15903
- type: "button",
15904
- onClick: p.onToggleCompactToolbar,
15905
- className: cn(
15906
- "px-2 py-1 rounded text-[11px] transition-colors mr-1",
15907
- isCompactToolbarOpen ? "bg-primary/80 text-white" : "text-muted-foreground hover:text-foreground"
15908
- ),
15909
- title: t(
15910
- isCompactToolbarOpen ? "pptx.ribbon.collapseRibbon" : "pptx.ribbon.expandRibbon"
16428
+ showRibbon && /* @__PURE__ */ jsxs(
16429
+ "div",
16430
+ {
16431
+ role: "tablist",
16432
+ className: "flex items-center border-b border-border/60 px-1 max-md:overflow-x-auto max-md:scrollbar-none",
16433
+ children: [
16434
+ TOOLBAR_SECTIONS.map((s) => /* @__PURE__ */ jsx(
16435
+ "button",
16436
+ {
16437
+ type: "button",
16438
+ role: "tab",
16439
+ "aria-selected": toolbarSection === s.id,
16440
+ onClick: () => onSetToolbarSection(s.id),
16441
+ className: cn(
16442
+ "relative px-3.5 py-2 text-[12px] font-medium whitespace-nowrap transition-colors max-md:min-h-[36px] max-md:px-3",
16443
+ toolbarSection === s.id ? s.id === "file" ? "text-white bg-primary/80 rounded-sm" : "text-foreground after:absolute after:-bottom-px after:left-0 after:right-0 after:h-[2.5px] after:bg-primary" : s.id === "file" ? "text-primary hover:bg-primary/15 rounded-sm" : "text-muted-foreground hover:text-foreground hover:bg-accent/30"
16444
+ ),
16445
+ children: t(s.labelKey)
16446
+ },
16447
+ s.id
16448
+ )),
16449
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
16450
+ /* @__PURE__ */ jsx(
16451
+ TabRowActions,
16452
+ {
16453
+ onEnterRehearsalMode: p.canEdit ? p.onEnterRehearsalMode : void 0,
16454
+ onOpenShareDialog: p.onOpenShareDialog,
16455
+ onPackageForSharing: p.onPackageForSharing
16456
+ }
15911
16457
  ),
15912
- children: t(isCompactToolbarOpen ? "pptx.ribbon.collapseRibbon" : "pptx.ribbon.expandRibbon")
15913
- }
15914
- )
15915
- ] }),
16458
+ isNarrowViewport && /* @__PURE__ */ jsx(
16459
+ "button",
16460
+ {
16461
+ type: "button",
16462
+ onClick: p.onToggleCompactToolbar,
16463
+ className: cn(
16464
+ "px-2 py-1 rounded text-[11px] transition-colors mr-1",
16465
+ isCompactToolbarOpen ? "bg-primary/80 text-white" : "text-muted-foreground hover:text-foreground"
16466
+ ),
16467
+ title: t(
16468
+ isCompactToolbarOpen ? "pptx.ribbon.collapseRibbon" : "pptx.ribbon.expandRibbon"
16469
+ ),
16470
+ children: t(isCompactToolbarOpen ? "pptx.ribbon.collapseRibbon" : "pptx.ribbon.expandRibbon")
16471
+ }
16472
+ )
16473
+ ]
16474
+ }
16475
+ ),
15916
16476
  showRibbon && /* @__PURE__ */ jsxs(
15917
16477
  "div",
15918
16478
  {
@@ -20689,6 +21249,8 @@ function SmartArtLayoutSwitcher({
20689
21249
  layoutType: updated.layoutType,
20690
21250
  resolvedLayoutType: updated.resolvedLayoutType,
20691
21251
  layout: updated.layout,
21252
+ layoutDirty: updated.layoutDirty,
21253
+ drawingDirty: updated.drawingDirty,
20692
21254
  drawingShapes: updated.drawingShapes
20693
21255
  });
20694
21256
  };
@@ -20701,6 +21263,7 @@ function SmartArtLayoutSwitcher({
20701
21263
  "button",
20702
21264
  {
20703
21265
  type: "button",
21266
+ "data-testid": `smartart-layout-${type}`,
20704
21267
  disabled: !canEdit,
20705
21268
  className: cn(
20706
21269
  "flex flex-col items-center gap-0.5 rounded border p-1.5 transition-colors",
@@ -20841,6 +21404,7 @@ function SmartArtNodeRow({
20841
21404
  {
20842
21405
  ref: inputRef,
20843
21406
  type: "text",
21407
+ "data-testid": "smartart-node-text",
20844
21408
  disabled: !canEdit,
20845
21409
  "aria-label": label,
20846
21410
  className: cn(INPUT, "flex-1 text-[11px] py-0.5"),
@@ -21057,6 +21621,7 @@ function SmartArtPropertiesPanel({
21057
21621
  "select",
21058
21622
  {
21059
21623
  disabled: !canEdit,
21624
+ "data-testid": "smartart-color-scheme",
21060
21625
  "aria-label": t("pptx.smartart.colorScheme"),
21061
21626
  className: cn(INPUT, "w-full"),
21062
21627
  value: smartArtData.colorScheme ?? "colorful1",
@@ -22296,6 +22861,42 @@ function InspectorCommentsSection({
22296
22861
  ] })
22297
22862
  ] });
22298
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
+ }
22299
22900
  function InfoRow({ label, value }) {
22300
22901
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
22301
22902
  /* @__PURE__ */ jsx("span", { children: label }),
@@ -22680,8 +23281,23 @@ function SlideThemeOverridePanel({
22680
23281
  }) {
22681
23282
  const { t } = useTranslation();
22682
23283
  const override = activeSlide?.clrMapOverride;
22683
- const isOverrideActive = hasNonTrivialOverride(override);
23284
+ const isOverrideActive = override !== void 0;
22684
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
+ );
22685
23301
  const handleToggle = useCallback(
22686
23302
  (enabled) => {
22687
23303
  if (enabled) {
@@ -22689,12 +23305,12 @@ function SlideThemeOverridePanel({
22689
23305
  for (const key of COLOR_MAP_ALIAS_KEYS) {
22690
23306
  defaultMap[key] = DEFAULT_COLOR_MAP[key];
22691
23307
  }
22692
- onUpdateSlide({ clrMapOverride: defaultMap });
23308
+ commitOverride(defaultMap);
22693
23309
  } else {
22694
- onUpdateSlide({ clrMapOverride: void 0 });
23310
+ commitOverride(void 0);
22695
23311
  }
22696
23312
  },
22697
- [onUpdateSlide]
23313
+ [commitOverride]
22698
23314
  );
22699
23315
  const handleAliasChange = useCallback(
22700
23316
  (alias, targetSlot) => {
@@ -22705,9 +23321,9 @@ function SlideThemeOverridePanel({
22705
23321
  next[key] = DEFAULT_COLOR_MAP[key];
22706
23322
  }
22707
23323
  }
22708
- onUpdateSlide({ clrMapOverride: next });
23324
+ commitOverride(next);
22709
23325
  },
22710
- [override, onUpdateSlide]
23326
+ [commitOverride, override]
22711
23327
  );
22712
23328
  const resolveSlotColor = useCallback(
22713
23329
  (slot) => {
@@ -23142,6 +23758,35 @@ function TemplateBackgroundCard({
23142
23758
  })()
23143
23759
  ] });
23144
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
+ }
23145
23790
  function InspectorPane(props) {
23146
23791
  const {
23147
23792
  isOpen,
@@ -23206,23 +23851,13 @@ function InspectorPane(props) {
23206
23851
  const hasSelection = selectedElement !== null;
23207
23852
  const { t } = useTranslation();
23208
23853
  const { dragY, handlers: dragHandlers } = useSheetDismissDrag(onClose);
23209
- const [selectedThemePath, setSelectedThemePath] = useState("");
23210
- const ANIM_MIN = 80;
23211
- const ANIM_MAX = 500;
23212
- const ANIM_DEFAULT = 220;
23213
- const [animPanelHeight, setAnimPanelHeight] = useState(ANIM_DEFAULT);
23214
- const onResizeAnim = useCallback((delta) => {
23215
- setAnimPanelHeight((h) => Math.min(ANIM_MAX, Math.max(ANIM_MIN, h - delta)));
23216
- }, []);
23217
- useEffect(() => {
23218
- const activeThemePath = slideMasters?.[0]?.themePath;
23219
- if (activeThemePath && activeThemePath.length > 0) {
23220
- setSelectedThemePath(activeThemePath);
23221
- return;
23222
- }
23223
- const fallback = themeOptions[0]?.path ?? "";
23224
- setSelectedThemePath((previous) => previous || fallback);
23225
- }, [slideMasters, themeOptions]);
23854
+ const {
23855
+ animationPanelHeight,
23856
+ effectiveThemeOptions,
23857
+ onResizeAnimationPanel,
23858
+ selectedThemePath,
23859
+ setSelectedThemePath
23860
+ } = useInspectorPaneState(themeOptions, slideMasters, theme);
23226
23861
  return /* @__PURE__ */ jsxs(Fragment, { children: [
23227
23862
  isOpen && /* @__PURE__ */ jsx(
23228
23863
  "button",
@@ -23264,35 +23899,14 @@ function InspectorPane(props) {
23264
23899
  children: /* @__PURE__ */ jsx("div", { className: "h-1 w-10 rounded-full bg-muted-foreground/40" })
23265
23900
  }
23266
23901
  ),
23267
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-2 border-b border-border", children: [
23268
- /* @__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(
23269
- "button",
23270
- {
23271
- type: "button",
23272
- title: label,
23273
- className: cn(
23274
- "flex items-center gap-1 px-2 py-1 rounded text-[11px] transition-colors",
23275
- activeTab === key ? "bg-primary text-white" : "text-muted-foreground hover:text-foreground hover:bg-accent"
23276
- ),
23277
- onClick: () => onSetActiveTab(key),
23278
- children: [
23279
- /* @__PURE__ */ jsx(Icon, { className: "w-3.5 h-3.5" }),
23280
- /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: label })
23281
- ]
23282
- },
23283
- key
23284
- )) }),
23285
- /* @__PURE__ */ jsx(
23286
- "button",
23287
- {
23288
- type: "button",
23289
- onClick: onClose,
23290
- title: t("common.close"),
23291
- className: "p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted transition-colors",
23292
- children: /* @__PURE__ */ jsx(LuX, { className: "w-4 h-4" })
23293
- }
23294
- )
23295
- ] }),
23902
+ /* @__PURE__ */ jsx(
23903
+ InspectorPaneHeader,
23904
+ {
23905
+ activeTab,
23906
+ onSetActiveTab,
23907
+ onClose
23908
+ }
23909
+ ),
23296
23910
  /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto p-3 space-y-3", children: [
23297
23911
  activeTab === "elements" && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
23298
23912
  /* @__PURE__ */ jsx("div", { className: cn(HEADING, "mb-2"), children: t("pptx.inspector.layerOrder") }),
@@ -23345,7 +23959,7 @@ function InspectorPane(props) {
23345
23959
  coreProperties,
23346
23960
  appProperties,
23347
23961
  customProperties,
23348
- themeOptions,
23962
+ themeOptions: effectiveThemeOptions,
23349
23963
  selectedThemePath,
23350
23964
  setSelectedThemePath,
23351
23965
  onApplyTheme,
@@ -23402,12 +24016,12 @@ function InspectorPane(props) {
23402
24016
  )
23403
24017
  ] }),
23404
24018
  hasSelection && selectedElement && activeSlide && /* @__PURE__ */ jsxs(Fragment, { children: [
23405
- /* @__PURE__ */ jsx(ResizeHandle, { direction: "vertical", onResize: onResizeAnim }),
24019
+ /* @__PURE__ */ jsx(ResizeHandle, { direction: "vertical", onResize: onResizeAnimationPanel }),
23406
24020
  /* @__PURE__ */ jsx(
23407
24021
  "div",
23408
24022
  {
23409
24023
  className: "border-t border-border p-3 overflow-y-auto flex-shrink-0",
23410
- style: { height: animPanelHeight },
24024
+ style: { height: animationPanelHeight },
23411
24025
  children: /* @__PURE__ */ jsx(
23412
24026
  AnimationPanel,
23413
24027
  {
@@ -23968,93 +24582,17 @@ function PresentationTouchControls({
23968
24582
  },
23969
24583
  "aria-label": t("pptx.presenter.nextSlide"),
23970
24584
  children: /* @__PURE__ */ jsx(LuChevronRight, { size: 26 })
23971
- }
23972
- ),
23973
- /* @__PURE__ */ jsx(
23974
- "span",
23975
- {
23976
- className: "fixed z-[90] left-1/2 -translate-x-1/2 px-3 py-1 rounded-full bg-black/55 text-white text-xs font-mono tabular-nums pointer-events-none",
23977
- style: { bottom: "calc(env(safe-area-inset-bottom, 0px) + 0.5rem)" },
23978
- children: totalSlides === 0 ? "0 / 0" : `${currentSlideIndex + 1} / ${totalSlides}`
23979
- }
23980
- )
23981
- ] });
23982
- }
23983
- function PresentationSubtitleBar({
23984
- visible
23985
- }) {
23986
- const { t } = useTranslation();
23987
- const [captionText, setCaptionText] = useState("");
23988
- const [supportState, setSupportState] = useState(
23989
- "unknown"
23990
- );
23991
- const recognitionRef = useRef(null);
23992
- const shouldRunRef = useRef(false);
23993
- useEffect(() => {
23994
- if (!visible) {
23995
- shouldRunRef.current = false;
23996
- recognitionRef.current?.stop();
23997
- recognitionRef.current = null;
23998
- setCaptionText("");
23999
- return;
24000
- }
24001
- shouldRunRef.current = true;
24002
- const speechWindow = window;
24003
- const RecognitionCtor = speechWindow.SpeechRecognition ?? speechWindow.webkitSpeechRecognition;
24004
- if (!RecognitionCtor) {
24005
- setSupportState("unsupported");
24006
- return;
24007
- }
24008
- setSupportState("supported");
24009
- const recognition = new RecognitionCtor();
24010
- recognition.continuous = true;
24011
- recognition.interimResults = true;
24012
- recognition.lang = navigator.language || "en-US";
24013
- recognition.onresult = (event) => {
24014
- let finalText = "";
24015
- let interimText = "";
24016
- for (let index = event.resultIndex; index < event.results.length; index += 1) {
24017
- const result = event.results[index];
24018
- const fragment = result?.[0]?.transcript ?? "";
24019
- if (result?.isFinal) {
24020
- finalText += fragment;
24021
- } else {
24022
- interimText += fragment;
24023
- }
24024
- }
24025
- const merged = `${finalText} ${interimText}`.trim();
24026
- if (merged.length > 0) {
24027
- setCaptionText(merged);
24028
- }
24029
- };
24030
- recognition.onerror = () => {
24031
- };
24032
- recognition.onend = () => {
24033
- if (!shouldRunRef.current) {
24034
- return;
24035
- }
24036
- try {
24037
- recognition.start();
24038
- } catch {
24039
- }
24040
- };
24041
- recognitionRef.current = recognition;
24042
- try {
24043
- recognition.start();
24044
- } catch {
24045
- setSupportState("unsupported");
24046
- }
24047
- return () => {
24048
- shouldRunRef.current = false;
24049
- recognition.stop();
24050
- recognitionRef.current = null;
24051
- };
24052
- }, [visible]);
24053
- if (!visible) {
24054
- return null;
24055
- }
24056
- const renderedText = supportState === "unsupported" ? t("pptx.subtitles.notSupported") : captionText.length > 0 ? captionText : t("pptx.subtitles.listening");
24057
- 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 }) }) });
24585
+ }
24586
+ ),
24587
+ /* @__PURE__ */ jsx(
24588
+ "span",
24589
+ {
24590
+ className: "fixed z-[90] left-1/2 -translate-x-1/2 px-3 py-1 rounded-full bg-black/55 text-white text-xs font-mono tabular-nums pointer-events-none",
24591
+ style: { bottom: "calc(env(safe-area-inset-bottom, 0px) + 0.5rem)" },
24592
+ children: totalSlides === 0 ? "0 / 0" : `${currentSlideIndex + 1} / ${totalSlides}`
24593
+ }
24594
+ )
24595
+ ] });
24058
24596
  }
24059
24597
  function SlideLayer({ slide, templateElements, canvasSize }) {
24060
24598
  const safeWidth = Math.max(canvasSize.width, 1);
@@ -24402,6 +24940,7 @@ function PresentationTransitionOverlay({
24402
24940
  "div",
24403
24941
  {
24404
24942
  ref: containerRef,
24943
+ "data-pptx-transition-overlay": true,
24405
24944
  className: "pptx-react-transition-overlay absolute inset-0 pointer-events-none overflow-hidden",
24406
24945
  style: { zIndex: outgoingZIndex },
24407
24946
  children: [
@@ -24409,6 +24948,7 @@ function PresentationTransitionOverlay({
24409
24948
  /* @__PURE__ */ jsx(
24410
24949
  "div",
24411
24950
  {
24951
+ "data-pptx-transition-layer": "outgoing",
24412
24952
  className: "pptx-react-transition-layer absolute inset-0 flex items-center justify-center",
24413
24953
  style: {
24414
24954
  animation: animations.outgoing !== "none" ? animations.outgoing : void 0
@@ -25480,6 +26020,7 @@ function BroadcastDialog({
25480
26020
  const [copied, setCopied] = useState(false);
25481
26021
  const dialogRef = useRef(null);
25482
26022
  const { panelStyle, handlers: dragHandlers } = useModalDismissDrag(onClose);
26023
+ useModalFocus(open, dialogRef, onClose);
25483
26024
  useEffect(() => {
25484
26025
  if (open && !isBroadcasting) {
25485
26026
  const broadcastRoom = defaultRoomId ? `broadcast-${defaultRoomId}` : generateBroadcastRoomId();
@@ -25488,23 +26029,6 @@ function BroadcastDialog({
25488
26029
  setServerUrl(defaultServerUrl ?? DEFAULT_BROADCAST_SERVER_URL);
25489
26030
  }
25490
26031
  }, [open, isBroadcasting, defaultRoomId, defaultUserName, defaultServerUrl]);
25491
- useEffect(() => {
25492
- if (!open) {
25493
- return;
25494
- }
25495
- function handleKeyDown(e) {
25496
- if (e.key === "Escape") {
25497
- onClose();
25498
- }
25499
- }
25500
- document.addEventListener("keydown", handleKeyDown);
25501
- return () => document.removeEventListener("keydown", handleKeyDown);
25502
- }, [open, onClose]);
25503
- useEffect(() => {
25504
- if (open && dialogRef.current) {
25505
- dialogRef.current.focus();
25506
- }
25507
- }, [open]);
25508
26032
  const activeRoomId = isBroadcasting ? collab.config.roomId : roomId;
25509
26033
  const activeServerUrl = isBroadcasting ? collab.config.serverUrl : serverUrl;
25510
26034
  const broadcastUrl = typeof window !== "undefined" ? buildBroadcastViewerUrl(activeRoomId, activeServerUrl, window.location) : activeRoomId;
@@ -25790,24 +26314,252 @@ function ActiveBroadcastView({
25790
26314
  }
25791
26315
  function getInitials3(name) {
25792
26316
  const parts = name.trim().split(/\s+/u);
25793
- if (parts.length >= 2) {
25794
- return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
25795
- }
25796
- return name.slice(0, 2).toUpperCase();
26317
+ return parts.length >= 2 ? (parts[0][0] + parts[parts.length - 1][0]).toUpperCase() : name.slice(0, 2).toUpperCase();
25797
26318
  }
25798
26319
  function isP2P(config) {
25799
26320
  return config.transport === "webrtc" || !config.serverUrl?.trim();
25800
26321
  }
25801
- function buildCollabShareUrl(config) {
25802
- if (typeof window === "undefined") {
25803
- return config.roomId;
25804
- }
25805
- const base = `${window.location.origin}${window.location.pathname}`;
25806
- const room = encodeURIComponent(config.roomId);
25807
- if (isP2P(config)) {
25808
- return `${base}?room=${room}&transport=webrtc`;
25809
- }
25810
- return `${base}?room=${room}&server=${encodeURIComponent(config.serverUrl ?? "")}`;
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") })
26556
+ ] });
26557
+ }
26558
+ function shareUrl2(config) {
26559
+ return buildCollaborationShareUrl(
26560
+ config,
26561
+ typeof window === "undefined" ? void 0 : { origin: window.location.origin, pathname: window.location.pathname }
26562
+ );
25811
26563
  }
25812
26564
  function ShareDialog({
25813
26565
  open,
@@ -25826,55 +26578,34 @@ function ShareDialog({
25826
26578
  const [roomId, setRoomId] = useState(defaultRoomId ?? "");
25827
26579
  const [userName, setUserName] = useState(defaultUserName ?? "");
25828
26580
  const [serverUrl, setServerUrl] = useState(defaultServerUrl ?? "");
26581
+ const [mode, setMode] = useState("create");
26582
+ const [invitation, setInvitation] = useState("");
25829
26583
  const [copied, setCopied] = useState(false);
25830
26584
  const dialogRef = useRef(null);
25831
26585
  const { panelStyle, handlers: dragHandlers } = useModalDismissDrag(onClose);
26586
+ useModalFocus(open, dialogRef, onClose);
25832
26587
  useEffect(() => {
25833
26588
  if (activeCollaboration) {
25834
- setRoomId(activeCollaboration.roomId);
25835
- setUserName(activeCollaboration.userName);
25836
- setServerUrl(activeCollaboration.serverUrl);
25837
- }
25838
- }, [activeCollaboration]);
25839
- useEffect(() => {
25840
- if (!open) {
25841
- return;
25842
- }
25843
- function handleKeyDown(e) {
25844
- if (e.key === "Escape") {
25845
- onClose();
25846
- }
25847
- }
25848
- document.addEventListener("keydown", handleKeyDown);
25849
- return () => document.removeEventListener("keydown", handleKeyDown);
25850
- }, [open, onClose]);
25851
- useEffect(() => {
25852
- if (open && dialogRef.current) {
25853
- dialogRef.current.focus();
26589
+ setRoomId(activeCollaboration.roomId);
26590
+ setUserName(activeCollaboration.userName);
26591
+ setServerUrl(activeCollaboration.serverUrl);
25854
26592
  }
25855
- }, [open]);
26593
+ }, [activeCollaboration]);
25856
26594
  const handleCopyRoomId = useCallback(() => {
25857
26595
  const config = activeCollaboration ?? { roomId, serverUrl };
25858
- void navigator.clipboard.writeText(buildCollabShareUrl(config)).then(() => {
26596
+ void navigator.clipboard.writeText(shareUrl2(config)).then(() => {
25859
26597
  setCopied(true);
25860
26598
  setTimeout(() => setCopied(false), 2e3);
25861
26599
  return void 0;
25862
26600
  });
25863
26601
  }, [activeCollaboration, roomId, serverUrl]);
25864
26602
  const handleStartSharing = useCallback(() => {
25865
- if (!roomId.trim() || !userName.trim()) {
25866
- return;
26603
+ const config = mode === "join" ? buildJoinCollaborationConfig({ invitation, userName, serverUrl }) : buildCreateCollaborationConfig({ roomId, userName, serverUrl });
26604
+ if (config) {
26605
+ onStartCollaboration?.(config);
25867
26606
  }
25868
- const trimmedServer = serverUrl.trim();
25869
- onStartCollaboration?.({
25870
- roomId: roomId.trim(),
25871
- serverUrl: trimmedServer,
25872
- userName: userName.trim(),
25873
- // A blank server selects the serverless peer-to-peer transport.
25874
- transport: resolveTransportForServerUrl(trimmedServer)
25875
- });
25876
- }, [roomId, userName, serverUrl, onStartCollaboration]);
25877
- 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;
25878
26609
  if (!open) {
25879
26610
  return null;
25880
26611
  }
@@ -25937,12 +26668,16 @@ function ShareDialog({
25937
26668
  ) : /* @__PURE__ */ jsx(
25938
26669
  StartSessionForm,
25939
26670
  {
26671
+ mode,
26672
+ invitation,
25940
26673
  roomId,
25941
26674
  userName,
25942
26675
  serverUrl,
25943
26676
  onRoomIdChange: setRoomId,
25944
26677
  onUserNameChange: setUserName,
25945
26678
  onServerUrlChange: setServerUrl,
26679
+ onModeChange: setMode,
26680
+ onInvitationChange: setInvitation,
25946
26681
  preconfigured,
25947
26682
  connectionFailed: collab?.status === "error",
25948
26683
  onRetry: collab?.retry
@@ -25965,7 +26700,7 @@ function ShareDialog({
25965
26700
  disabled: !canStart,
25966
26701
  onClick: handleStartSharing,
25967
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",
25968
- children: t("pptx.share.startSharing")
26703
+ children: t(mode === "join" ? "pptx.share.joinSession" : "pptx.share.startSharing")
25969
26704
  }
25970
26705
  )
25971
26706
  ] })
@@ -25976,199 +26711,6 @@ function ShareDialog({
25976
26711
  )
25977
26712
  ] });
25978
26713
  }
25979
- function StartSessionForm({
25980
- roomId,
25981
- userName,
25982
- serverUrl,
25983
- onRoomIdChange,
25984
- onUserNameChange,
25985
- onServerUrlChange,
25986
- preconfigured,
25987
- connectionFailed,
25988
- onRetry
25989
- }) {
25990
- const { t } = useTranslation();
25991
- const inputReadOnlyClass = preconfigured ? " opacity-70 cursor-not-allowed" : "";
25992
- return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
25993
- /* @__PURE__ */ jsx("p", { className: "text-[13px] text-muted-foreground leading-relaxed", children: preconfigured ? t("pptx.share.preconfiguredDescription") : t("pptx.share.description") }),
25994
- connectionFailed && /* @__PURE__ */ jsxs(
25995
- "div",
25996
- {
25997
- role: "alert",
25998
- 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",
25999
- children: [
26000
- /* @__PURE__ */ jsx(LuWifiOff, { className: "w-4 h-4 mt-0.5 shrink-0" }),
26001
- /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-1", children: [
26002
- /* @__PURE__ */ jsx("p", { className: "font-medium", children: t("pptx.share.connectionError") }),
26003
- onRetry && /* @__PURE__ */ jsx(
26004
- "button",
26005
- {
26006
- type: "button",
26007
- onClick: onRetry,
26008
- className: "underline underline-offset-2 hover:no-underline",
26009
- children: t("pptx.collaboration.retry")
26010
- }
26011
- )
26012
- ] })
26013
- ]
26014
- }
26015
- ),
26016
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
26017
- /* @__PURE__ */ jsx("label", { htmlFor: "share-room-id", className: "block text-[12px] font-medium text-foreground", children: t("pptx.share.sessionName") }),
26018
- /* @__PURE__ */ jsx(
26019
- "input",
26020
- {
26021
- id: "share-room-id",
26022
- type: "text",
26023
- "aria-label": t("pptx.share.sessionName"),
26024
- value: roomId,
26025
- onChange: (e) => onRoomIdChange(e.target.value),
26026
- readOnly: preconfigured,
26027
- placeholder: t("pptx.share.sessionPlaceholder"),
26028
- 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}`
26029
- }
26030
- ),
26031
- /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t("pptx.share.sessionHint") })
26032
- ] }),
26033
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
26034
- /* @__PURE__ */ jsx("label", { htmlFor: "share-user-name", className: "block text-[12px] font-medium text-foreground", children: t("pptx.share.displayName") }),
26035
- /* @__PURE__ */ jsx(
26036
- "input",
26037
- {
26038
- id: "share-user-name",
26039
- type: "text",
26040
- "aria-label": t("pptx.share.displayName"),
26041
- value: userName,
26042
- onChange: (e) => onUserNameChange(e.target.value),
26043
- readOnly: preconfigured,
26044
- placeholder: t("pptx.share.namePlaceholder"),
26045
- 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}`
26046
- }
26047
- )
26048
- ] }),
26049
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
26050
- /* @__PURE__ */ jsx("label", { htmlFor: "share-server-url", className: "block text-[12px] font-medium text-foreground", children: t("pptx.share.serverLabel") }),
26051
- /* @__PURE__ */ jsx(
26052
- "input",
26053
- {
26054
- id: "share-server-url",
26055
- type: "text",
26056
- "aria-label": t("pptx.share.serverLabel"),
26057
- value: serverUrl,
26058
- onChange: (e) => onServerUrlChange(e.target.value),
26059
- readOnly: preconfigured,
26060
- placeholder: t("pptx.share.serverPlaceholder"),
26061
- 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}`
26062
- }
26063
- )
26064
- ] }),
26065
- /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: serverUrl.trim().length === 0 ? t("pptx.share.p2pHint") : t("pptx.share.serverHint") })
26066
- ] });
26067
- }
26068
- function ActiveSessionView({
26069
- collab,
26070
- activeCollaboration,
26071
- copied,
26072
- onCopyRoomId,
26073
- onStopCollaboration
26074
- }) {
26075
- const { t } = useTranslation();
26076
- const statusColor = collab.status === "connected" ? "text-green-400" : collab.status === "connecting" ? "text-yellow-400" : "text-red-400";
26077
- const statusIcon = collab.status === "connected" || collab.status === "connecting" ? /* @__PURE__ */ jsx(LuWifi, { className: "w-4 h-4" }) : /* @__PURE__ */ jsx(LuWifiOff, { className: "w-4 h-4" });
26078
- return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
26079
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
26080
- /* @__PURE__ */ jsx("span", { className: statusColor, children: statusIcon }),
26081
- /* @__PURE__ */ jsx("span", { className: "text-[13px] font-medium text-foreground capitalize", children: collab.status }),
26082
- /* @__PURE__ */ jsxs("span", { className: "text-[12px] text-muted-foreground ml-auto flex items-center gap-1", children: [
26083
- /* @__PURE__ */ jsx(LuUsers, { className: "w-3.5 h-3.5" }),
26084
- t("pptx.collaboration.userCount", { count: collab.connectedCount })
26085
- ] })
26086
- ] }),
26087
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
26088
- /* @__PURE__ */ jsx("label", { className: "block text-[12px] font-medium text-foreground", children: t("pptx.share.shareLink") }),
26089
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
26090
- /* @__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) }),
26091
- /* @__PURE__ */ jsx(
26092
- "button",
26093
- {
26094
- type: "button",
26095
- onClick: onCopyRoomId,
26096
- 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",
26097
- title: t("pptx.share.copyLink"),
26098
- children: copied ? /* @__PURE__ */ jsxs(Fragment, { children: [
26099
- /* @__PURE__ */ jsx(LuCheck, { className: "w-3.5 h-3.5 text-green-400" }),
26100
- /* @__PURE__ */ jsx("span", { children: t("pptx.share.copied") })
26101
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
26102
- /* @__PURE__ */ jsx(LuCopy, { className: "w-3.5 h-3.5" }),
26103
- /* @__PURE__ */ jsx("span", { children: t("pptx.share.copyUrl") })
26104
- ] })
26105
- }
26106
- )
26107
- ] }),
26108
- /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t("pptx.share.shareHint") })
26109
- ] }),
26110
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-[11px] text-muted-foreground", children: [
26111
- /* @__PURE__ */ jsxs("span", { children: [
26112
- t("pptx.share.room"),
26113
- " ",
26114
- /* @__PURE__ */ jsx("code", { className: "font-mono text-foreground", children: collab.config.roomId })
26115
- ] }),
26116
- /* @__PURE__ */ jsxs("span", { children: [
26117
- t("pptx.share.server"),
26118
- " ",
26119
- /* @__PURE__ */ jsx("code", { className: "font-mono text-foreground", children: isP2P(collab.config) ? t("pptx.share.p2pServerValue") : collab.config.serverUrl })
26120
- ] })
26121
- ] }),
26122
- collab.remoteUsers.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
26123
- /* @__PURE__ */ jsx("label", { className: "block text-[12px] font-medium text-foreground", children: t("pptx.share.connectedUsers") }),
26124
- /* @__PURE__ */ jsxs("div", { className: "rounded border border-border bg-background divide-y divide-border max-h-[140px] overflow-y-auto", children: [
26125
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [
26126
- /* @__PURE__ */ jsx(
26127
- "div",
26128
- {
26129
- className: "w-6 h-6 rounded-full flex items-center justify-center text-[9px] font-semibold text-white shrink-0",
26130
- style: { backgroundColor: collab.config.userColor ?? "#6366f1" },
26131
- children: getInitials3(activeCollaboration?.userName ?? collab.config.userName)
26132
- }
26133
- ),
26134
- /* @__PURE__ */ jsx("span", { className: "text-[12px] text-foreground truncate", children: activeCollaboration?.userName ?? collab.config.userName }),
26135
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground ml-auto", children: t("pptx.share.you") })
26136
- ] }),
26137
- collab.remoteUsers.map((user) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [
26138
- /* @__PURE__ */ jsx(
26139
- "div",
26140
- {
26141
- className: "w-6 h-6 rounded-full flex items-center justify-center text-[9px] font-semibold text-white shrink-0",
26142
- style: { backgroundColor: user.userColor },
26143
- children: user.userAvatar ? /* @__PURE__ */ jsx(
26144
- "img",
26145
- {
26146
- src: user.userAvatar,
26147
- alt: "",
26148
- className: "w-full h-full rounded-full object-cover"
26149
- }
26150
- ) : getInitials3(user.userName)
26151
- }
26152
- ),
26153
- /* @__PURE__ */ jsx("span", { className: "text-[12px] text-foreground truncate", children: user.userName }),
26154
- /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground ml-auto", children: [
26155
- "Slide ",
26156
- user.activeSlideIndex + 1
26157
- ] })
26158
- ] }, user.clientId))
26159
- ] })
26160
- ] }),
26161
- onStopCollaboration && /* @__PURE__ */ jsx(
26162
- "button",
26163
- {
26164
- type: "button",
26165
- onClick: onStopCollaboration,
26166
- 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",
26167
- children: t("pptx.share.stopSharing")
26168
- }
26169
- )
26170
- ] });
26171
- }
26172
26714
  function HyperlinkTabContent({
26173
26715
  targetType,
26174
26716
  url,
@@ -27790,7 +28332,7 @@ function SlideCanvas({
27790
28332
  // pinch-zoom. View/present mode keeps the default so the slide can
27791
28333
  // still be scrolled and swipe-navigated.
27792
28334
  touchAction: isEditableCanvas ? "none" : void 0,
27793
- ...getSlideBackgroundStyle(activeSlide)
28335
+ ...getReactSlideBackgroundStyle(activeSlide)
27794
28336
  },
27795
28337
  onClick: handleStageClick,
27796
28338
  onDoubleClick: handleStageDblClick,
@@ -28117,6 +28659,7 @@ function ViewerInspector({
28117
28659
  return /* @__PURE__ */ jsx(
28118
28660
  "div",
28119
28661
  {
28662
+ "data-pptx-inspector": true,
28120
28663
  role: "complementary",
28121
28664
  "aria-label": t("pptx.inspector.properties"),
28122
28665
  className: "h-full flex-shrink-0 max-md:absolute max-md:inset-x-0 max-md:bottom-0 max-md:top-auto max-md:z-30",
@@ -28969,10 +29512,11 @@ function EquationEditorDialog({
28969
29512
  const [latex, setLatex] = useState(initialLatex);
28970
29513
  const deferredLatex = useDeferredValue(latex);
28971
29514
  const textareaRef = useRef(null);
29515
+ const dialogRef = useRef(null);
29516
+ useModalFocus(isOpen, dialogRef, onClose, textareaRef);
28972
29517
  useEffect(() => {
28973
29518
  if (isOpen) {
28974
29519
  setLatex(initialLatex);
28975
- setTimeout(() => textareaRef.current?.focus(), 50);
28976
29520
  }
28977
29521
  }, [isOpen, initialLatex]);
28978
29522
  const { mathml, omml } = useMemo(() => {
@@ -29036,11 +29580,13 @@ function EquationEditorDialog({
29036
29580
  children: /* @__PURE__ */ jsxs(
29037
29581
  "div",
29038
29582
  {
29583
+ ref: dialogRef,
29039
29584
  style: panelStyle,
29040
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)]",
29041
29586
  onClick: (e) => e.stopPropagation(),
29042
29587
  role: "dialog",
29043
29588
  "aria-modal": "true",
29589
+ tabIndex: -1,
29044
29590
  "aria-label": isEditing ? t("pptx.equation.editTitle", "Edit Equation") : t("pptx.equation.insertTitle", "Insert Equation"),
29045
29591
  children: [
29046
29592
  /* @__PURE__ */ jsxs(
@@ -29056,6 +29602,7 @@ function EquationEditorDialog({
29056
29602
  type: "button",
29057
29603
  onClick: onClose,
29058
29604
  className: "p-1 rounded hover:bg-accent text-muted-foreground hover:text-foreground transition-colors",
29605
+ "aria-label": t("pptx.settings.close"),
29059
29606
  children: /* @__PURE__ */ jsx(LuX, { className: "w-4 h-4" })
29060
29607
  }
29061
29608
  )
@@ -29190,6 +29737,8 @@ function InsertSmartArtDialog({
29190
29737
  const { panelStyle, handlers: dragHandlers } = useModalDismissDrag(onClose);
29191
29738
  const [activeCategory, setActiveCategory] = useState("list");
29192
29739
  const [selectedLayout, setSelectedLayout] = useState(null);
29740
+ const dialogRef = useRef(null);
29741
+ useModalFocus(isOpen, dialogRef, onClose);
29193
29742
  const filteredPresets = PRESETS.filter((p) => p.category === activeCategory);
29194
29743
  const handleInsert = useCallback(() => {
29195
29744
  if (!selectedLayout) {
@@ -29224,11 +29773,13 @@ function InsertSmartArtDialog({
29224
29773
  children: /* @__PURE__ */ jsxs(
29225
29774
  "div",
29226
29775
  {
29776
+ ref: dialogRef,
29227
29777
  style: panelStyle,
29228
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)]",
29229
29779
  role: "dialog",
29230
29780
  "aria-modal": "true",
29231
29781
  "aria-label": t("pptx.smartart.insertTitle"),
29782
+ tabIndex: -1,
29232
29783
  children: [
29233
29784
  /* @__PURE__ */ jsxs(
29234
29785
  "div",
@@ -29251,43 +29802,60 @@ function InsertSmartArtDialog({
29251
29802
  }
29252
29803
  ),
29253
29804
  /* @__PURE__ */ jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
29254
- /* @__PURE__ */ jsx("div", { className: "w-40 border-r border-border py-2", children: CATEGORIES.map((cat) => /* @__PURE__ */ jsx(
29255
- "button",
29805
+ /* @__PURE__ */ jsx(
29806
+ "nav",
29256
29807
  {
29257
- type: "button",
29258
- onClick: () => {
29259
- setActiveCategory(cat.id);
29260
- setSelectedLayout(null);
29261
- },
29262
- className: cn(
29263
- "w-full text-left px-3 py-1.5 text-xs transition-colors",
29264
- activeCategory === cat.id ? "bg-primary text-white" : "text-foreground hover:bg-muted"
29265
- ),
29266
- children: t(cat.label)
29267
- },
29268
- cat.id
29269
- )) }),
29270
- /* @__PURE__ */ jsx("div", { className: "flex-1 p-3 overflow-y-auto", children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-2", children: filteredPresets.map((preset) => /* @__PURE__ */ jsxs(
29271
- "button",
29808
+ className: "w-40 border-r border-border py-2",
29809
+ "aria-label": t("pptx.insertSmartArt.categories"),
29810
+ children: CATEGORIES.map((cat) => /* @__PURE__ */ jsx(
29811
+ "button",
29812
+ {
29813
+ type: "button",
29814
+ onClick: () => {
29815
+ setActiveCategory(cat.id);
29816
+ setSelectedLayout(null);
29817
+ },
29818
+ className: cn(
29819
+ "w-full text-left px-3 py-1.5 text-xs transition-colors",
29820
+ activeCategory === cat.id ? "bg-primary text-white" : "text-foreground hover:bg-muted"
29821
+ ),
29822
+ children: t(cat.label)
29823
+ },
29824
+ cat.id
29825
+ ))
29826
+ }
29827
+ ),
29828
+ /* @__PURE__ */ jsx("div", { className: "flex-1 p-3 overflow-y-auto", children: /* @__PURE__ */ jsx(
29829
+ "div",
29272
29830
  {
29273
- type: "button",
29274
- onClick: () => setSelectedLayout(preset.layout),
29275
- onDoubleClick: () => {
29276
- setSelectedLayout(preset.layout);
29277
- onInsert(preset.layout, preset.defaultItems);
29278
- onClose();
29279
- },
29280
- className: cn(
29281
- "flex flex-col items-center gap-1 p-2 rounded border transition-colors",
29282
- selectedLayout === preset.layout ? "border-primary bg-primary/20" : "border-border hover:border-border hover:bg-muted/50"
29283
- ),
29284
- children: [
29285
- /* @__PURE__ */ jsx("div", { className: "w-16 h-12 flex items-center justify-center bg-muted rounded", children: getPreviewForLayout(preset.layout) }),
29286
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-foreground text-center leading-tight", children: t(preset.labelKey) })
29287
- ]
29288
- },
29289
- preset.layout
29290
- )) }) })
29831
+ className: "grid grid-cols-3 gap-2",
29832
+ role: "listbox",
29833
+ "aria-label": t("pptx.insertSmartArt.layouts"),
29834
+ children: filteredPresets.map((preset) => /* @__PURE__ */ jsxs(
29835
+ "button",
29836
+ {
29837
+ type: "button",
29838
+ role: "option",
29839
+ "aria-selected": selectedLayout === preset.layout,
29840
+ onClick: () => setSelectedLayout(preset.layout),
29841
+ onDoubleClick: () => {
29842
+ setSelectedLayout(preset.layout);
29843
+ onInsert(preset.layout, preset.defaultItems);
29844
+ onClose();
29845
+ },
29846
+ className: cn(
29847
+ "flex flex-col items-center gap-1 p-2 rounded border transition-colors",
29848
+ selectedLayout === preset.layout ? "border-primary bg-primary/20" : "border-border hover:border-border hover:bg-muted/50"
29849
+ ),
29850
+ children: [
29851
+ /* @__PURE__ */ jsx("div", { className: "w-16 h-12 flex items-center justify-center bg-muted rounded", children: getPreviewForLayout(preset.layout) }),
29852
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-foreground text-center leading-tight", children: t(preset.labelKey) })
29853
+ ]
29854
+ },
29855
+ preset.layout
29856
+ ))
29857
+ }
29858
+ ) })
29291
29859
  ] }),
29292
29860
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2 px-4 py-3 border-t border-border", children: [
29293
29861
  /* @__PURE__ */ jsx(
@@ -29510,9 +30078,19 @@ function ViewerDialogGroup(props) {
29510
30078
  ] });
29511
30079
  }
29512
30080
  function ViewerPresentationLayer(props) {
29513
- 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;
29514
30091
  const presenterActive = mode === "present" && presentation.presenterMode;
29515
30092
  return /* @__PURE__ */ jsxs(Fragment, { children: [
30093
+ mode === "present" && !presentation.presenterMode && /* @__PURE__ */ jsx(PresentationAudienceEffects, { snapshot: presentation.presenterSnapshot }),
29516
30094
  presenterActive && (isMobile ? /* @__PURE__ */ jsx(
29517
30095
  MobilePresenterView,
29518
30096
  {
@@ -29536,7 +30114,20 @@ function ViewerPresentationLayer(props) {
29536
30114
  onExit: onExitPresentation,
29537
30115
  onOpenAudienceWindow: presentation.openAudienceWindow,
29538
30116
  onCloseAudienceWindow: presentation.closeAudienceWindow,
29539
- 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
29540
30131
  }
29541
30132
  )),
29542
30133
  mode === "present" && presentation.rehearsing && /* @__PURE__ */ jsx(
@@ -29714,7 +30305,7 @@ function ViewerCanvasArea(props) {
29714
30305
  {
29715
30306
  handoutMaster: s.handoutMaster,
29716
30307
  canvasSize,
29717
- slidesPerPage: s.handoutSlidesPerPage
30308
+ slidesPerPage: s.handoutMaster?.slidesPerPage ?? s.handoutSlidesPerPage
29718
30309
  }
29719
30310
  ) : /* @__PURE__ */ jsx(
29720
30311
  SlideCanvas,
@@ -30761,6 +31352,11 @@ function ThemeGallery({
30761
31352
  const { t } = useTranslation();
30762
31353
  const [selectedTheme, setSelectedTheme] = useState(currentTheme ?? null);
30763
31354
  const fileInputRef = useRef(null);
31355
+ useEffect(() => {
31356
+ if (open) {
31357
+ setSelectedTheme(currentTheme ?? null);
31358
+ }
31359
+ }, [currentTheme, open]);
30764
31360
  if (!open) {
30765
31361
  return null;
30766
31362
  }
@@ -30888,6 +31484,9 @@ function ViewerSidePanels(props) {
30888
31484
  onResizeRight
30889
31485
  } = props;
30890
31486
  const effectiveSlide = mode === "master" ? masterPseudoSlide : activeSlide;
31487
+ const currentBuiltInTheme = BUILT_IN_THEMES.find(
31488
+ (candidate) => themeColorSchemesEqual(s.theme?.colorScheme, candidate.colorScheme)
31489
+ ) ?? null;
30891
31490
  return /* @__PURE__ */ jsxs(Fragment, { children: [
30892
31491
  (mode === "edit" || mode === "master") && s.isInspectorPaneOpen && onResizeRight && /* @__PURE__ */ jsx(ResizeHandle, { direction: "horizontal", onResize: onResizeRight }),
30893
31492
  /* @__PURE__ */ jsx(
@@ -30985,7 +31584,7 @@ function ViewerSidePanels(props) {
30985
31584
  ThemeGallery,
30986
31585
  {
30987
31586
  open: s.isThemeGalleryOpen,
30988
- currentTheme: null,
31587
+ currentTheme: currentBuiltInTheme,
30989
31588
  canEdit,
30990
31589
  onClose: () => s.setIsThemeGalleryOpen(false),
30991
31590
  onApplyTheme: (theme) => {
@@ -31089,12 +31688,28 @@ function ViewerMainContent(props) {
31089
31688
  masterViewTab: state.masterViewTab,
31090
31689
  notesMaster: state.notesMaster,
31091
31690
  handoutMaster: state.handoutMaster,
31092
- handoutSlidesPerPage: state.handoutSlidesPerPage,
31691
+ handoutSlidesPerPage: state.handoutMaster?.slidesPerPage ?? state.handoutSlidesPerPage,
31093
31692
  onSelectMaster: dialogs.handleSelectMaster,
31094
31693
  onSelectLayout: dialogs.handleSelectLayout,
31095
- onCollapse: () => state.setIsSlidesPaneOpen(false),
31694
+ onCollapse: dialogs.handleCloseMasterView,
31096
31695
  onTabChange: state.setMasterViewTab,
31097
- 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
+ }
31098
31713
  }
31099
31714
  ),
31100
31715
  /* @__PURE__ */ jsx(
@@ -31225,6 +31840,7 @@ function MobileBottomBar({
31225
31840
  active ? "text-primary" : "text-muted-foreground hover:text-foreground"
31226
31841
  ),
31227
31842
  "aria-pressed": active,
31843
+ "aria-label": key === "notes" ? t("pptx.statusBar.toggleNotes") : void 0,
31228
31844
  children: [
31229
31845
  /* @__PURE__ */ jsx(Icon, { className: "w-5 h-5" }),
31230
31846
  /* @__PURE__ */ jsx("span", { children: t(label) }),
@@ -31825,6 +32441,7 @@ var PowerPointViewer = forwardRef(
31825
32441
  {
31826
32442
  style: themeStyle,
31827
32443
  "data-pptx-viewer": "",
32444
+ "aria-busy": loading,
31828
32445
  className: "h-full w-full bg-background text-foreground relative",
31829
32446
  children: [
31830
32447
  /* @__PURE__ */ jsx(
@@ -32051,6 +32668,7 @@ var PowerPointViewer = forwardRef(
32051
32668
  templateElements: state.templateElements,
32052
32669
  presentation,
32053
32670
  onExitPresentation: () => handleSetMode("edit"),
32671
+ onUpdateNotes: propertyHandlers.handleUpdateNotes,
32054
32672
  isMobile
32055
32673
  }
32056
32674
  )