@a3s-lab/office 0.32.0 → 0.34.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 (57) hide show
  1. package/README.md +62 -8
  2. package/dist/0~3539.js +2 -2
  3. package/dist/0~3557.js +1 -1
  4. package/dist/0~3635.js +1 -1
  5. package/dist/0~5809.js +123 -0
  6. package/dist/0~7048.js +1 -2
  7. package/dist/{0~7614.js → 0~7828.js} +376 -5
  8. package/dist/{0~6090.js → 0~8136.js} +71 -2
  9. package/dist/{0~4595.js → 0~9073.js} +3 -72
  10. package/dist/0~9445.js +1 -1
  11. package/dist/0~document-editor.js +2 -2
  12. package/dist/0~markdown-editor.js +2 -2
  13. package/dist/0~pdf-page-organization-engine.js +187 -0
  14. package/dist/0~pdf-viewer.js +1018 -30
  15. package/dist/0~presentation-editor.js +580 -68
  16. package/dist/0~spreadsheet-editor.js +2 -2
  17. package/dist/0~work-pptx-export.js +50 -14
  18. package/dist/0~work-pptx-import.js +21 -9
  19. package/dist/4121.js +122 -1
  20. package/dist/4560.js +33 -0
  21. package/dist/core.d.ts +1 -0
  22. package/dist/internal/features/work/editors/pdf-editor-extensions.d.ts +11 -0
  23. package/dist/internal/features/work/editors/pdf-page-organization-engine.d.ts +2 -0
  24. package/dist/internal/features/work/editors/pdf-page-organization-plan.d.ts +10 -0
  25. package/dist/internal/features/work/editors/pdf-page-organization-types.d.ts +68 -0
  26. package/dist/internal/features/work/editors/pdf-page-organization-worker-client.d.ts +2 -0
  27. package/dist/internal/features/work/editors/pdf-page-organization-worker-protocol.d.ts +13 -0
  28. package/dist/internal/features/work/editors/pdf-page-organization.d.ts +3 -0
  29. package/dist/internal/features/work/editors/pdf-page-organization.worker.d.ts +1 -0
  30. package/dist/internal/features/work/editors/pdf-page-organizer-dialog.d.ts +17 -0
  31. package/dist/internal/features/work/editors/pdf-thumbnail-rail.d.ts +4 -0
  32. package/dist/internal/features/work/editors/pdf-toolbar.d.ts +2 -1
  33. package/dist/internal/features/work/editors/pdf-viewer.d.ts +3 -1
  34. package/dist/internal/features/work/editors/presentation-animation-panel.d.ts +11 -0
  35. package/dist/internal/features/work/editors/presentation-command-types.d.ts +16 -1
  36. package/dist/internal/features/work/editors/presentation-presenter-view.d.ts +2 -1
  37. package/dist/internal/features/work/editors/presentation-slide-canvas.d.ts +11 -3
  38. package/dist/internal/features/work/editors/use-pdf-page-organization.d.ts +38 -0
  39. package/dist/internal/features/work/editors/use-presentation-animation-commands.d.ts +15 -0
  40. package/dist/internal/features/work/work-pptx-animation.d.ts +13 -0
  41. package/dist/internal/features/work/work-pptx-groups.d.ts +4 -0
  42. package/dist/internal/features/work/work-presentation-animation-constraints.d.ts +4 -0
  43. package/dist/internal/features/work/work-presentation-animation.d.ts +20 -0
  44. package/dist/internal/features/work/work-presentation-clipboard.d.ts +10 -3
  45. package/dist/internal/features/work/work-types.d.ts +17 -0
  46. package/dist/office-kernel.wasm +0 -0
  47. package/dist/pdf-page-organization.worker.js +19667 -0
  48. package/dist/pdf-page-organization.worker.js.LICENSE.txt +14 -0
  49. package/dist/react.d.ts +1 -0
  50. package/dist/styles.css +438 -0
  51. package/dist/vue.d.ts +3 -1
  52. package/dist/vue.js +2 -0
  53. package/dist/web-component.d.ts +3 -1
  54. package/dist/web-component.js +9 -0
  55. package/docs/latest/en/browser-editor-architecture.md +29 -6
  56. package/package.json +8 -1
  57. package/dist/0~work-presentation-transition.js +0 -38
@@ -1,7 +1,7 @@
1
1
  import { __webpack_require__ } from "./0~rslib-runtime.js";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
4
- import { AlignCenter, AlignHorizontalSpaceBetween, AlignLeft, AlignRight, AlignVerticalSpaceBetween, ArrowDownToLine, ArrowUpToLine, BarChart3, Bold, ChevronLeft, ChevronRight, ClipboardPaste, Cloud, Copy, CopyCheck, CopyPlus, GalleryVerticalEnd, Grid2X2, Group, Image, Italic, LayoutTemplate, Link2, MapPin, Maximize2, MessageSquarePlus, MessagesSquare, PanelsTopLeft, Pause, Play, Plus, Presentation, Redo2, RotateCcw, Scissors, Square, SquarePlay, Trash2, Type, Underline, Undo2, Ungroup, X } from "lucide-react";
4
+ import { AlignCenter, AlignHorizontalSpaceBetween, AlignLeft, AlignRight, AlignVerticalSpaceBetween, ArrowDown, ArrowDownToLine, ArrowUp, ArrowUpToLine, BarChart3, Bold, ChevronLeft, ChevronRight, ClipboardPaste, Cloud, Copy, CopyCheck, CopyPlus, GalleryVerticalEnd, Grid2X2, Group, Image, Italic, LayoutTemplate, Link2, MapPin, Maximize2, MessageSquarePlus, MessagesSquare, PanelsTopLeft, Pause, Play, Plus, Presentation, Redo2, RotateCcw, Scissors, Square, SquarePlay, Trash2, Type, Underline, Undo2, Ungroup, X } from "lucide-react";
5
5
  import extension_color from "@tiptap/extension-color";
6
6
  import extension_placeholder from "@tiptap/extension-placeholder";
7
7
  import { TextStyle } from "@tiptap/extension-text-style";
@@ -15,13 +15,13 @@ import { createOfficeId as createWorkId } from "./4121.js";
15
15
  import { OfficeTableInsertPopover, readOfficeFileAsDataUrl } from "./0~9445.js";
16
16
  import { normalizePresentationChartLegendPosition, presentationChartDataLabelPositions, presentationChartDataLabelText, normalizePresentationBubbleSizeRepresents, parsePresentationChartCategories, presentationChartXValues, withPresentationChartLayout, presentationChartTypeLabel, presentationChartAxes, withPresentationChartSeriesAnalysis, presentationChartUsesNumericXAxis, presentationChartSupportsSeriesMarkers, presentationChartBubbleSizes, createPresentationChartElement, parsePresentationChartValueDraft, normalizePresentationChartDataLabelPosition, presentationChartSupportsAxisTitles, withPresentationChartDataLabels, withPresentationChartType, createPresentationChartSeries, presentationChartErrorBarCount, normalizePresentationScatterStyle, withPresentationChartSeriesStyle, normalizePresentationBubbleScale, presentationChartDataLabelPositionLabel, presentationChartShowsLegend, presentationChartTrendlineCount, presentationChartHasDataLabels, normalizeDoughnutHoleSize, withPresentationChartAxes } from "./0~work-presentation-charts.js";
17
17
  import { spreadsheetChartSeriesLayout, workSpreadsheetChartSupportsGrouping, normalizeWorkSpreadsheetChartSmoothLines, workSpreadsheetChartAxisIsValueAxis, fitSpreadsheetTrendline, spreadsheetChartSeriesLegendColor, normalizeWorkSpreadsheetChartSeriesStyle, spreadsheetChartCategoryVisualIndex, errorBarSourceValues, normalizeWorkSpreadsheetErrorBars, workSpreadsheetChartSupportsTrendlines, normalizeWorkSpreadsheetChartLegendOverlay, normalizeWorkSpreadsheetChartGapWidth, spreadsheetChartCategoryLabelVisible, spreadsheetChartAxisLabelLayout, workSpreadsheetChartSupportsSmoothLines, workSpreadsheetChartGroupingIsStacked, normalizeWorkSpreadsheetChartGrouping, workSpreadsheetChartSupportsBarSpacing, workSpreadsheetChartSupportsErrorBars, spreadsheetChartSeriesLineStyle, workSpreadsheetChartAxisDefaultLabelPosition, workSpreadsheetChartSupportsSeriesAnalysis, workSpreadsheetChartAxisIsCategoryAxis, spreadsheetErrorBarAmounts, spreadsheetChartAxisValueRatio, spreadsheetChartAxisScale, normalizeWorkSpreadsheetChartOverlap, spreadsheetChartBarGeometry, workSpreadsheetChartAxisShowsMajorGridlinesByDefault, spreadsheetChartAxisGridlinesVisible, normalizeWorkSpreadsheetTrendline, roundChartNumber, formatSpreadsheetChartAxisNumber, spreadsheetChartSeriesFillStyle } from "./8715.js";
18
- import { officePresenceColorStyle, office_text_field_OfficeTextArea, stepOfficeZoom, useOfficeEditorWheelZoom, useOfficeRemoteParticipants, button_Button, useOfficePublishPresenceLocation, CommittedOfficeTextArea, CommittedOfficeTextField, office_text_field_OfficeTextField, useDialogFocusScope, icon_button_IconButton, office_text_field_OfficeFileInput } from "./0~6090.js";
18
+ import { officePresenceColorStyle, office_text_field_OfficeTextArea, stepOfficeZoom, useOfficeEditorWheelZoom, useOfficeRemoteParticipants, button_Button, useOfficePublishPresenceLocation, CommittedOfficeTextArea, CommittedOfficeTextField, office_text_field_OfficeTextField, useDialogFocusScope, icon_button_IconButton, office_text_field_OfficeFileInput } from "./0~8136.js";
19
19
  import { useOfficeTaskPaneEscape, normalizeOfficeFontFamily, OfficeCheckbox, CommittedOfficeNumberField, useOfficeTaskPaneModal, officeFontFamilyLabel, useOfficeDialog, officeFontFamilies, CollectionState, createOfficeKernelClient, OfficeNumberField, handleOfficeTaskPaneKeyDown } from "./0~7048.js";
20
- import { WorkspaceContextMenu, isWorkspaceContextMenuKeyboardEvent, WorkOfficeZoomControls, WorkOfficeStatusBar, WorkOfficeRibbon, workspaceContextMenuPosition, OfficeSelect, WorkOfficeRibbonGroup, WorkOfficePreviewBar, WorkOfficeRibbonButton } from "./0~4595.js";
20
+ import { WorkspaceContextMenu, isWorkspaceContextMenuKeyboardEvent, WorkOfficeZoomControls, WorkOfficeStatusBar, WorkOfficeRibbon, workspaceContextMenuPosition, OfficeSelect, WorkOfficeRibbonGroup, WorkOfficePreviewBar, WorkOfficeRibbonButton } from "./0~9073.js";
21
21
  import { presentationAgentProposalTargets, SpreadsheetChartSeriesStyleEditor, applyPresentationAgentProposalChanges, normalizeOptionalOfficeNumber, SpreadsheetErrorBarEditor, useOfficeHistory, presentationNotesProposalTarget, presentationAgentSelection, SpreadsheetTrendlineEditor, presentationAgentMenuItems } from "./0~3539.js";
22
+ import { slideTransitionDurationMilliseconds, workSlideAnimationIndex, workSlideAnimationForElement, remapWorkSlideAnimations, createWorkSlideTransition, workSlideTransitionsEqual, workSlideAnimationCues, initialWorkSlideAnimationCueIndex, normalizeWorkSlideAnimation, createWorkSlideAnimation, removeWorkSlideAnimationsForElements } from "./0~5809.js";
22
23
  import { createOfficeEditorExtension, useOfficeEditorRuntime, useOfficeEditorKeyboardShortcuts } from "./0~3557.js";
23
24
  import { isOfficeShortcutBlocked, OfficeColorPicker } from "./0~3635.js";
24
- import { slideTransitionDurationMilliseconds, createWorkSlideTransition, workSlideTransitionsEqual } from "./0~work-presentation-transition.js";
25
25
  import { normalizeDocumentHref, DOCUMENT_LINK_VALIDATION_MESSAGE } from "./0~work-document-links.js";
26
26
  import { showToast } from "./6489.js";
27
27
  import { scaledPresentationVisuals } from "./0~work-presentation-visual-scale.js";
@@ -1397,14 +1397,19 @@ function normalizePresentationChartPercent(value, minimum, maximum) {
1397
1397
  const PRESENTATION_OBJECT_OFFSET_STEP = 5;
1398
1398
  const PRESENTATION_CLIPBOARD_MAX_OFFSET = 20;
1399
1399
  let work_presentation_clipboard_clipboard = null;
1400
- function copyPresentationElements(elements) {
1400
+ function copyPresentationElements(elements, animations = []) {
1401
1401
  if (!elements.length) return;
1402
+ const elementIds = new Set(elements.map((element)=>element.id));
1403
+ const selectedAnimations = animations.filter((animation)=>elementIds.has(animation.elementId));
1402
1404
  work_presentation_clipboard_clipboard = {
1403
1405
  payload: {
1404
1406
  kind: 'elements',
1405
1407
  elements: work_presentation_clipboard_structuredCopy([
1406
1408
  ...elements
1407
- ])
1409
+ ]),
1410
+ ...selectedAnimations.length ? {
1411
+ animations: work_presentation_clipboard_structuredCopy(selectedAnimations)
1412
+ } : {}
1408
1413
  },
1409
1414
  pasteCount: 0
1410
1415
  };
@@ -1431,42 +1436,48 @@ function takePresentationClipboard() {
1431
1436
  function hasPresentationClipboard() {
1432
1437
  return null !== work_presentation_clipboard_clipboard;
1433
1438
  }
1434
- function clonePresentationElementForPaste(element, offset) {
1435
- const copy = remapPresentationGroupPaths([
1436
- work_presentation_clipboard_structuredCopy(element)
1437
- ])[0];
1438
- return {
1439
- ...copy,
1440
- id: createWorkId('element'),
1441
- x: work_presentation_clipboard_clamp(copy.x + offset, 0, Math.max(0, 100 - copy.width)),
1442
- y: work_presentation_clipboard_clamp(copy.y + offset, 0, Math.max(0, 100 - copy.height)),
1443
- placeholder: void 0
1439
+ function clonePresentationElementsAndAnimationsForPaste(elements, animations, offset) {
1440
+ if (!elements.length) return {
1441
+ elements: []
1444
1442
  };
1445
- }
1446
- function clonePresentationElementsForPaste(elements, offset) {
1447
- if (!elements.length) return [];
1448
1443
  const right = Math.max(...elements.map((element)=>element.x + element.width));
1449
1444
  const bottom = Math.max(...elements.map((element)=>element.y + element.height));
1450
1445
  const offsetX = work_presentation_clipboard_clamp(offset, 0, Math.max(0, 100 - right));
1451
1446
  const offsetY = work_presentation_clipboard_clamp(offset, 0, Math.max(0, 100 - bottom));
1452
- return remapPresentationGroupPaths(work_presentation_clipboard_structuredCopy(elements)).map((element)=>({
1447
+ const elementIds = new Map();
1448
+ const copies = remapPresentationGroupPaths(work_presentation_clipboard_structuredCopy(elements)).map((element)=>{
1449
+ const id = createWorkId('element');
1450
+ elementIds.set(element.id, id);
1451
+ return {
1453
1452
  ...element,
1454
- id: createWorkId('element'),
1453
+ id,
1455
1454
  x: element.x + offsetX,
1456
1455
  y: element.y + offsetY,
1457
1456
  placeholder: void 0
1458
- }));
1457
+ };
1458
+ });
1459
+ return {
1460
+ elements: copies,
1461
+ animations: remapWorkSlideAnimations(animations, elementIds)
1462
+ };
1459
1463
  }
1460
1464
  function clonePresentationSlideForPaste(slide, existingSlideNames = []) {
1461
1465
  const copy = work_presentation_clipboard_structuredCopy(slide);
1466
+ const elementIds = new Map();
1467
+ const elements = remapPresentationGroupPaths(copy.elements).map((element)=>{
1468
+ const id = createWorkId('element');
1469
+ elementIds.set(element.id, id);
1470
+ return {
1471
+ ...element,
1472
+ id
1473
+ };
1474
+ });
1462
1475
  return {
1463
1476
  ...copy,
1464
1477
  id: createWorkId('slide'),
1465
1478
  name: nextPresentationSlideCopyName(slide.name, existingSlideNames),
1466
- elements: remapPresentationGroupPaths(copy.elements).map((element)=>({
1467
- ...element,
1468
- id: createWorkId('element')
1469
- })),
1479
+ elements,
1480
+ animations: remapWorkSlideAnimations(copy.animations, elementIds),
1470
1481
  comments: copy.comments?.map((comment)=>({
1471
1482
  ...comment,
1472
1483
  id: createWorkId('slide-comment')
@@ -1720,6 +1731,27 @@ function createPresentationEditorExtensions() {
1720
1731
  }
1721
1732
  })
1722
1733
  }),
1734
+ createOfficeEditorExtension({
1735
+ name: 'presentationAnimations',
1736
+ addCommands: ()=>({
1737
+ moveEntranceAnimation: {
1738
+ canExecute: ({ animations }, direction)=>animations.canMoveEntranceAnimation(direction),
1739
+ execute: ({ animations }, direction)=>animations.moveEntranceAnimation(direction)
1740
+ },
1741
+ previewAnimations: {
1742
+ canExecute: ({ animations })=>animations.canPreviewAnimations,
1743
+ execute: ({ animations })=>animations.previewAnimations()
1744
+ },
1745
+ setEntranceAnimation: {
1746
+ canExecute: ({ animations })=>animations.canSetEntranceAnimation,
1747
+ execute: ({ animations }, effect)=>animations.setEntranceAnimation(effect)
1748
+ },
1749
+ updateEntranceAnimation: {
1750
+ canExecute: ({ animations })=>animations.canUpdateEntranceAnimation,
1751
+ execute: ({ animations }, patch)=>animations.updateEntranceAnimation(patch)
1752
+ }
1753
+ })
1754
+ }),
1723
1755
  createOfficeEditorExtension({
1724
1756
  name: 'presentationElements',
1725
1757
  addCommands: ()=>({
@@ -2443,17 +2475,20 @@ function presentationCommandsWithObjectFocus(commands, restoreFocus) {
2443
2475
  distributeElements: afterSuccessfulCommand(commands.distributeElements),
2444
2476
  duplicateSlide: afterSuccessfulCommand(commands.duplicateSlide),
2445
2477
  groupElements: afterSuccessfulCommand(commands.groupElements),
2478
+ moveEntranceAnimation: afterSuccessfulCommand(commands.moveEntranceAnimation),
2446
2479
  pasteSelection: afterSuccessfulCommand(commands.pasteSelection),
2447
2480
  redo: afterSuccessfulCommand(commands.redo),
2448
2481
  reorderElement: afterSuccessfulCommand(commands.reorderElement),
2449
2482
  setTransition: afterSuccessfulCommand(commands.setTransition),
2483
+ setEntranceAnimation: afterSuccessfulCommand(commands.setEntranceAnimation),
2450
2484
  setViewMode: afterSuccessfulCommand(commands.setViewMode),
2451
2485
  toggleBold: afterSuccessfulCommand(commands.toggleBold),
2452
2486
  toggleItalic: afterSuccessfulCommand(commands.toggleItalic),
2453
2487
  toggleUnderline: afterSuccessfulCommand(commands.toggleUnderline),
2454
2488
  undo: afterSuccessfulCommand(commands.undo),
2455
2489
  ungroupElements: afterSuccessfulCommand(commands.ungroupElements),
2456
- updateElement: afterSuccessfulCommand(commands.updateElement)
2490
+ updateElement: afterSuccessfulCommand(commands.updateElement),
2491
+ updateEntranceAnimation: afterSuccessfulCommand(commands.updateEntranceAnimation)
2457
2492
  };
2458
2493
  }
2459
2494
  function presentationChartAnalysisBounds(chart, xValues) {
@@ -3572,7 +3607,7 @@ function drawRadarChart(context, chart, sourceRect) {
3572
3607
  });
3573
3608
  }
3574
3609
  }
3575
- function SlideCanvas({ content, designContent, slide, interactive, aspectRatio, showPlaceholders = false }) {
3610
+ function SlideCanvas({ content, designContent, slide, interactive, aspectRatio, animationCueIndex, showPlaceholders = false }) {
3576
3611
  const view = designContent ? presentationSlideViewFromDesign(designContent, slide) : content ? presentationSlideView(content, slide) : void 0;
3577
3612
  const elements = [
3578
3613
  ...view?.inheritedElements.map((element)=>({
@@ -3584,6 +3619,7 @@ function SlideCanvas({ content, designContent, slide, interactive, aspectRatio,
3584
3619
  origin: 'slide'
3585
3620
  }))
3586
3621
  ];
3622
+ const animationPlayback = void 0 === animationCueIndex ? void 0 : slideAnimationPlayback(slide.animations, animationCueIndex);
3587
3623
  return /*#__PURE__*/ jsx("span", {
3588
3624
  className: `work-slide-canvas ${interactive ? 'interactive' : ''}`,
3589
3625
  style: {
@@ -3593,16 +3629,23 @@ function SlideCanvas({ content, designContent, slide, interactive, aspectRatio,
3593
3629
  children: elements.map(({ element, origin })=>/*#__PURE__*/ jsx(SlideElementPreview, {
3594
3630
  element: element,
3595
3631
  origin: origin,
3596
- showPlaceholder: showPlaceholders
3632
+ showPlaceholder: showPlaceholders,
3633
+ animationPlayback: 'slide' === origin ? animationPlayback?.get(element.id) : void 0
3597
3634
  }, `${origin}:${element.id}`))
3598
3635
  });
3599
3636
  }
3600
- function SlideElementPreview({ element, origin, showPlaceholder = false }) {
3637
+ function SlideElementPreview({ animationPlayback, element, origin, showPlaceholder = false }) {
3601
3638
  const hasRichText = element.textRuns?.some((run)=>run.text.length > 0);
3602
3639
  return /*#__PURE__*/ jsx("span", {
3603
3640
  className: `work-slide-element ${element.type} ${origin} ${showPlaceholder && element.placeholder ? 'placeholder' : ''}`.trim(),
3641
+ "data-slide-preview-element-id": element.id,
3604
3642
  "data-slide-element-origin": origin,
3605
- style: slideElementStyle(element),
3643
+ "data-slide-animation-effect": animationPlayback?.animation.effect,
3644
+ "data-slide-animation-state": animationPlayback?.state,
3645
+ style: {
3646
+ ...slideElementStyle(element),
3647
+ ...slideElementAnimationStyle(animationPlayback)
3648
+ },
3606
3649
  children: 'image' === element.type && element.image ? /*#__PURE__*/ jsx("img", {
3607
3650
  src: element.image.dataUrl,
3608
3651
  alt: element.altText ?? element.image.name
@@ -3658,6 +3701,8 @@ function slideElementStyle(element) {
3658
3701
  borderRadius: 'ellipse' === element.shapeType ? '50%' : `${element.radius ?? 0}%`,
3659
3702
  clipPath: shapeClipPath,
3660
3703
  opacity: element.opacity,
3704
+ '--work-slide-element-opacity': element.opacity ?? 1,
3705
+ '--work-slide-element-rotation': `${element.rotation ?? 0}deg`,
3661
3706
  transform: element.rotation ? `rotate(${element.rotation}deg)` : void 0,
3662
3707
  transformOrigin: 'center',
3663
3708
  ...'line' === element.type ? {
@@ -3666,6 +3711,27 @@ function slideElementStyle(element) {
3666
3711
  } : {}
3667
3712
  };
3668
3713
  }
3714
+ function slideAnimationPlayback(animations, activeCueIndex) {
3715
+ const playback = new Map();
3716
+ for (const [cueIndex, cue] of workSlideAnimationCues(animations).entries())for (const item of cue.items)playback.set(item.animation.elementId, {
3717
+ animation: item.animation,
3718
+ startOffsetMs: item.startOffsetMs,
3719
+ state: cueIndex < activeCueIndex ? 'finished' : cueIndex === activeCueIndex ? 'playing' : 'pending'
3720
+ });
3721
+ return playback;
3722
+ }
3723
+ function slideElementAnimationStyle(playback) {
3724
+ if (!playback) return {};
3725
+ const direction = playback.animation.direction ?? 'left';
3726
+ const translateX = 'left' === direction ? '-18%' : 'right' === direction ? '18%' : '0';
3727
+ const translateY = 'up' === direction ? '-18%' : 'down' === direction ? '18%' : '0';
3728
+ return {
3729
+ '--work-slide-animation-delay': `${playback.startOffsetMs}ms`,
3730
+ '--work-slide-animation-duration': `${playback.animation.durationMs}ms`,
3731
+ '--work-slide-animation-translate-x': translateX,
3732
+ '--work-slide-animation-translate-y': translateY
3733
+ };
3734
+ }
3669
3735
  function slideTextStyle(element) {
3670
3736
  return {
3671
3737
  color: element.color,
@@ -3741,7 +3807,7 @@ function createPresentationTimerController(now = Date.now()) {
3741
3807
  runningSinceMilliseconds: now
3742
3808
  };
3743
3809
  }
3744
- function PresentationPresenterView({ content, slide, nextSlide, index, total, aspectRatio, timer }) {
3810
+ function PresentationPresenterView({ animationCueIndex, content, slide, nextSlide, index, total, aspectRatio, timer }) {
3745
3811
  const [, setTimerRevision] = useState(0);
3746
3812
  const now = Date.now();
3747
3813
  const running = null !== timer.runningSinceMilliseconds;
@@ -3839,6 +3905,7 @@ function PresentationPresenterView({ content, slide, nextSlide, index, total, as
3839
3905
  children: slide.name
3840
3906
  }),
3841
3907
  /*#__PURE__*/ jsx(SlideCanvas, {
3908
+ animationCueIndex: animationCueIndex,
3842
3909
  content: content,
3843
3910
  slide: slide,
3844
3911
  interactive: false,
@@ -3901,6 +3968,7 @@ function formatDuration(seconds) {
3901
3968
  }
3902
3969
  function PresentationPlayer({ autoFullscreen = false, content, initialIndex = 0, onExit }) {
3903
3970
  const [playback, setPlayback] = useState({
3971
+ animationCueIndex: initialAnimationCueIndex(content.slides[presentationPlaybackIndex(initialIndex, content.slides.length)]),
3904
3972
  index: presentationPlaybackIndex(initialIndex, content.slides.length),
3905
3973
  transitionKey: 0
3906
3974
  });
@@ -3931,12 +3999,52 @@ function PresentationPlayer({ autoFullscreen = false, content, initialIndex = 0,
3931
3999
  setPlayback((current)=>{
3932
4000
  const index = Math.min(Math.max(current.index + delta, 0), content.slides.length - 1);
3933
4001
  return index === current.index ? current : {
4002
+ animationCueIndex: initialAnimationCueIndex(content.slides[index]),
4003
+ index,
4004
+ transitionKey: current.transitionKey + 1
4005
+ };
4006
+ });
4007
+ }, [
4008
+ content.slides
4009
+ ]);
4010
+ const advance = useCallback(()=>{
4011
+ setPlayback((current)=>{
4012
+ const currentSlide = content.slides[current.index];
4013
+ const cueCount = workSlideAnimationCues(currentSlide?.animations).length;
4014
+ if (current.animationCueIndex < cueCount - 1) return {
4015
+ ...current,
4016
+ animationCueIndex: current.animationCueIndex + 1
4017
+ };
4018
+ const index = Math.min(current.index + 1, content.slides.length - 1);
4019
+ if (index === current.index) return current;
4020
+ return {
4021
+ animationCueIndex: initialAnimationCueIndex(content.slides[index]),
4022
+ index,
4023
+ transitionKey: current.transitionKey + 1
4024
+ };
4025
+ });
4026
+ }, [
4027
+ content.slides
4028
+ ]);
4029
+ const retreat = useCallback(()=>{
4030
+ setPlayback((current)=>{
4031
+ const currentSlide = content.slides[current.index];
4032
+ const initialCueIndex = initialAnimationCueIndex(currentSlide);
4033
+ if (current.animationCueIndex > initialCueIndex) return {
4034
+ ...current,
4035
+ animationCueIndex: current.animationCueIndex - 1
4036
+ };
4037
+ const index = Math.max(current.index - 1, 0);
4038
+ if (index === current.index) return current;
4039
+ const previousCues = workSlideAnimationCues(content.slides[index]?.animations);
4040
+ return {
4041
+ animationCueIndex: previousCues.length - 1,
3934
4042
  index,
3935
4043
  transitionKey: current.transitionKey + 1
3936
4044
  };
3937
4045
  });
3938
4046
  }, [
3939
- content.slides.length
4047
+ content.slides
3940
4048
  ]);
3941
4049
  useEffect(()=>{
3942
4050
  const onKeyDown = (event)=>{
@@ -3950,12 +4058,12 @@ function PresentationPlayer({ autoFullscreen = false, content, initialIndex = 0,
3950
4058
  if ((' ' === event.key || 'Spacebar' === event.key) && presentationTargetActivatesWithSpace(event.target)) return;
3951
4059
  if ('ArrowRight' === event.key || 'ArrowDown' === event.key || 'PageDown' === event.key || ' ' === event.key || 'Spacebar' === event.key) {
3952
4060
  event.preventDefault();
3953
- move(1);
4061
+ advance();
3954
4062
  return;
3955
4063
  }
3956
4064
  if ('ArrowLeft' === event.key || 'ArrowUp' === event.key || 'PageUp' === event.key) {
3957
4065
  event.preventDefault();
3958
- move(-1);
4066
+ retreat();
3959
4067
  return;
3960
4068
  }
3961
4069
  if ('Home' === event.key) {
@@ -3971,9 +4079,11 @@ function PresentationPlayer({ autoFullscreen = false, content, initialIndex = 0,
3971
4079
  window.addEventListener('keydown', onKeyDown);
3972
4080
  return ()=>window.removeEventListener('keydown', onKeyDown);
3973
4081
  }, [
4082
+ advance,
3974
4083
  completeExit,
3975
4084
  content.slides.length,
3976
- move
4085
+ move,
4086
+ retreat
3977
4087
  ]);
3978
4088
  useLayoutEffect(()=>{
3979
4089
  const player = playerRef.current;
@@ -4024,6 +4134,8 @@ function PresentationPlayer({ autoFullscreen = false, content, initialIndex = 0,
4024
4134
  if (!slide) return null;
4025
4135
  const aspectRatio = `${content.width ?? 13.333} / ${content.height ?? 7.5}`;
4026
4136
  const transition = slide.transition;
4137
+ const animationCues = workSlideAnimationCues(slide.animations);
4138
+ const hasPendingAnimation = playback.animationCueIndex < animationCues.length - 1;
4027
4139
  const transitionStyle = {
4028
4140
  '--work-slide-transition-duration': `${slideTransitionDurationMilliseconds(transition)}ms`
4029
4141
  };
@@ -4034,6 +4146,7 @@ function PresentationPlayer({ autoFullscreen = false, content, initialIndex = 0,
4034
4146
  tabIndex: -1,
4035
4147
  children: [
4036
4148
  presenter ? /*#__PURE__*/ jsx(PresentationPresenterView, {
4149
+ animationCueIndex: playback.animationCueIndex,
4037
4150
  content: content,
4038
4151
  slide: slide,
4039
4152
  nextSlide: content.slides[playback.index + 1],
@@ -4048,8 +4161,8 @@ function PresentationPlayer({ autoFullscreen = false, content, initialIndex = 0,
4048
4161
  type: "button",
4049
4162
  className: "work-presentation-player-advance",
4050
4163
  "aria-label": "单击换到下一张幻灯片",
4051
- disabled: transition?.advanceOnClick === false || playback.index === content.slides.length - 1,
4052
- onClick: ()=>move(1)
4164
+ disabled: !hasPendingAnimation && (transition?.advanceOnClick === false || playback.index === content.slides.length - 1),
4165
+ onClick: ()=>advance()
4053
4166
  }),
4054
4167
  /*#__PURE__*/ jsx("div", {
4055
4168
  "aria-live": "polite",
@@ -4061,6 +4174,7 @@ function PresentationPlayer({ autoFullscreen = false, content, initialIndex = 0,
4061
4174
  "data-transition-speed": transition?.speed ?? 'medium',
4062
4175
  style: transitionStyle,
4063
4176
  children: /*#__PURE__*/ jsx(SlideCanvas, {
4177
+ animationCueIndex: playback.animationCueIndex,
4064
4178
  content: content,
4065
4179
  slide: slide,
4066
4180
  interactive: false,
@@ -4075,8 +4189,8 @@ function PresentationPlayer({ autoFullscreen = false, content, initialIndex = 0,
4075
4189
  type: "button",
4076
4190
  "aria-label": "上一张",
4077
4191
  "aria-keyshortcuts": "ArrowLeft ArrowUp PageUp",
4078
- disabled: 0 === playback.index,
4079
- onClick: ()=>move(-1),
4192
+ disabled: 0 === playback.index && playback.animationCueIndex <= initialAnimationCueIndex(slide),
4193
+ onClick: retreat,
4080
4194
  children: /*#__PURE__*/ jsx(ChevronLeft, {
4081
4195
  size: 18
4082
4196
  })
@@ -4092,8 +4206,8 @@ function PresentationPlayer({ autoFullscreen = false, content, initialIndex = 0,
4092
4206
  type: "button",
4093
4207
  "aria-label": "下一张",
4094
4208
  "aria-keyshortcuts": "ArrowRight ArrowDown PageDown Space",
4095
- disabled: playback.index === content.slides.length - 1,
4096
- onClick: ()=>move(1),
4209
+ disabled: !hasPendingAnimation && playback.index === content.slides.length - 1,
4210
+ onClick: advance,
4097
4211
  children: /*#__PURE__*/ jsx(ChevronRight, {
4098
4212
  size: 18
4099
4213
  })
@@ -4146,6 +4260,9 @@ function presentationPlaybackIndex(index, slideCount) {
4146
4260
  if (slideCount <= 1) return 0;
4147
4261
  return Math.min(slideCount - 1, Math.max(0, Math.trunc(index)));
4148
4262
  }
4263
+ function initialAnimationCueIndex(slide) {
4264
+ return initialWorkSlideAnimationCueIndex(workSlideAnimationCues(slide?.animations));
4265
+ }
4149
4266
  async function requestPresentationFullscreen(element) {
4150
4267
  if (!element.requestFullscreen) return false;
4151
4268
  try {
@@ -4594,6 +4711,233 @@ function commonRunValue(runs, getValue, fallback) {
4594
4711
  const values = runs.map((run)=>getValue(run) ?? fallback);
4595
4712
  return values.every((value)=>value === values[0]) ? values[0] : fallback;
4596
4713
  }
4714
+ function PresentationAnimationPanel({ animation, canMove, canPreview, editable, onMove, onPreview, onSetEffect, onUpdate }) {
4715
+ return /*#__PURE__*/ jsxs(Fragment, {
4716
+ children: [
4717
+ /*#__PURE__*/ jsx(WorkOfficeRibbonGroup, {
4718
+ label: "入场动画",
4719
+ children: /*#__PURE__*/ jsxs("fieldset", {
4720
+ className: "work-presentation-animation-options",
4721
+ children: [
4722
+ /*#__PURE__*/ jsx("legend", {
4723
+ className: "sr-only",
4724
+ children: "对象入场动画设置"
4725
+ }),
4726
+ /*#__PURE__*/ jsxs("div", {
4727
+ className: "work-office-field effect",
4728
+ children: [
4729
+ /*#__PURE__*/ jsx("span", {
4730
+ children: "效果"
4731
+ }),
4732
+ /*#__PURE__*/ jsx(OfficeSelect, {
4733
+ ariaLabel: "对象入场动画效果",
4734
+ disabled: !editable,
4735
+ value: animation?.effect ?? 'none',
4736
+ options: [
4737
+ {
4738
+ value: 'none',
4739
+ label: '无'
4740
+ },
4741
+ {
4742
+ value: 'appear',
4743
+ label: '出现'
4744
+ },
4745
+ {
4746
+ value: 'fade',
4747
+ label: '淡入'
4748
+ },
4749
+ {
4750
+ value: 'fly-in',
4751
+ label: '飞入'
4752
+ },
4753
+ {
4754
+ value: 'zoom',
4755
+ label: '缩放'
4756
+ }
4757
+ ],
4758
+ onValueChange: (effect)=>onSetEffect('none' === effect ? void 0 : effect)
4759
+ })
4760
+ ]
4761
+ }),
4762
+ /*#__PURE__*/ jsxs("div", {
4763
+ className: "work-office-field trigger",
4764
+ children: [
4765
+ /*#__PURE__*/ jsx("span", {
4766
+ children: "开始"
4767
+ }),
4768
+ /*#__PURE__*/ jsx(OfficeSelect, {
4769
+ ariaLabel: "对象入场动画触发方式",
4770
+ disabled: !animation,
4771
+ value: animation?.trigger ?? 'on-click',
4772
+ options: [
4773
+ {
4774
+ value: 'on-click',
4775
+ label: '单击时'
4776
+ },
4777
+ {
4778
+ value: 'with-previous',
4779
+ label: '与上一动画同时'
4780
+ },
4781
+ {
4782
+ value: 'after-previous',
4783
+ label: '上一动画之后'
4784
+ }
4785
+ ],
4786
+ onValueChange: (trigger)=>onUpdate({
4787
+ trigger: trigger
4788
+ })
4789
+ })
4790
+ ]
4791
+ }),
4792
+ animation?.effect === 'fly-in' && /*#__PURE__*/ jsxs("div", {
4793
+ className: "work-office-field direction",
4794
+ children: [
4795
+ /*#__PURE__*/ jsx("span", {
4796
+ children: "方向"
4797
+ }),
4798
+ /*#__PURE__*/ jsx(OfficeSelect, {
4799
+ ariaLabel: "对象飞入方向",
4800
+ value: animation.direction ?? 'left',
4801
+ options: [
4802
+ {
4803
+ value: 'left',
4804
+ label: '从左侧'
4805
+ },
4806
+ {
4807
+ value: 'right',
4808
+ label: '从右侧'
4809
+ },
4810
+ {
4811
+ value: 'up',
4812
+ label: '从上方'
4813
+ },
4814
+ {
4815
+ value: 'down',
4816
+ label: '从下方'
4817
+ }
4818
+ ],
4819
+ onValueChange: (direction)=>onUpdate({
4820
+ direction: direction
4821
+ })
4822
+ })
4823
+ ]
4824
+ })
4825
+ ]
4826
+ })
4827
+ }),
4828
+ /*#__PURE__*/ jsx(WorkOfficeRibbonGroup, {
4829
+ label: "动画计时",
4830
+ children: /*#__PURE__*/ jsxs("div", {
4831
+ className: "work-presentation-animation-timing",
4832
+ children: [
4833
+ /*#__PURE__*/ jsx(AnimationTimingField, {
4834
+ animationId: animation?.id,
4835
+ ariaLabel: "对象入场动画持续秒数",
4836
+ disabled: !animation,
4837
+ label: "持续",
4838
+ maximumMs: 60000,
4839
+ minimumMs: 100,
4840
+ valueMs: animation?.durationMs ?? 500,
4841
+ onCommit: (durationMs)=>onUpdate({
4842
+ durationMs
4843
+ })
4844
+ }),
4845
+ /*#__PURE__*/ jsx(AnimationTimingField, {
4846
+ animationId: animation?.id,
4847
+ ariaLabel: "对象入场动画延迟秒数",
4848
+ disabled: !animation,
4849
+ label: "延迟",
4850
+ maximumMs: 60000,
4851
+ minimumMs: 0,
4852
+ valueMs: animation?.delayMs ?? 0,
4853
+ onCommit: (delayMs)=>onUpdate({
4854
+ delayMs
4855
+ })
4856
+ })
4857
+ ]
4858
+ })
4859
+ }),
4860
+ /*#__PURE__*/ jsxs(WorkOfficeRibbonGroup, {
4861
+ label: "动画顺序",
4862
+ children: [
4863
+ /*#__PURE__*/ jsx(WorkOfficeRibbonButton, {
4864
+ label: "提前对象动画",
4865
+ visibleLabel: "提前",
4866
+ disabled: !canMove(-1),
4867
+ onClick: ()=>onMove(-1),
4868
+ children: /*#__PURE__*/ jsx(ArrowUp, {
4869
+ size: 19
4870
+ })
4871
+ }),
4872
+ /*#__PURE__*/ jsx(WorkOfficeRibbonButton, {
4873
+ label: "推后对象动画",
4874
+ visibleLabel: "推后",
4875
+ disabled: !canMove(1),
4876
+ onClick: ()=>onMove(1),
4877
+ children: /*#__PURE__*/ jsx(ArrowDown, {
4878
+ size: 19
4879
+ })
4880
+ })
4881
+ ]
4882
+ }),
4883
+ /*#__PURE__*/ jsx(WorkOfficeRibbonGroup, {
4884
+ label: "动画预览",
4885
+ children: /*#__PURE__*/ jsx(WorkOfficeRibbonButton, {
4886
+ label: "预览当前幻灯片动画",
4887
+ visibleLabel: "预览",
4888
+ disabled: !canPreview,
4889
+ onClick: onPreview,
4890
+ children: /*#__PURE__*/ jsx(Play, {
4891
+ size: 19
4892
+ })
4893
+ })
4894
+ })
4895
+ ]
4896
+ });
4897
+ }
4898
+ function AnimationTimingField({ animationId, ariaLabel, disabled, label, maximumMs, minimumMs, onCommit, valueMs }) {
4899
+ const canonical = animationSecondsDraft(valueMs);
4900
+ const [draft, setDraft] = useState(canonical);
4901
+ useEffect(()=>setDraft(canonical), [
4902
+ animationId,
4903
+ canonical
4904
+ ]);
4905
+ const commit = (value)=>{
4906
+ const next = normalizedAnimationMilliseconds(value, valueMs, minimumMs, maximumMs);
4907
+ setDraft(animationSecondsDraft(next));
4908
+ if (next !== valueMs) onCommit(next);
4909
+ };
4910
+ return /*#__PURE__*/ jsxs("div", {
4911
+ className: "work-office-field seconds",
4912
+ children: [
4913
+ /*#__PURE__*/ jsx("span", {
4914
+ children: label
4915
+ }),
4916
+ /*#__PURE__*/ jsx(OfficeNumberField, {
4917
+ ariaLabel: ariaLabel,
4918
+ disabled: disabled,
4919
+ min: minimumMs / 1000,
4920
+ max: maximumMs / 1000,
4921
+ step: 0.1,
4922
+ value: draft,
4923
+ escapeConsumer: draft !== canonical,
4924
+ onValueChange: setDraft,
4925
+ onCommit: commit,
4926
+ onCancel: ()=>setDraft(canonical)
4927
+ })
4928
+ ]
4929
+ });
4930
+ }
4931
+ function animationSecondsDraft(valueMs) {
4932
+ return String(valueMs / 1000);
4933
+ }
4934
+ function normalizedAnimationMilliseconds(value, current, minimum, maximum) {
4935
+ if (!value.trim()) return current;
4936
+ const seconds = Number(value);
4937
+ if (!Number.isFinite(seconds)) return current;
4938
+ const stepped = Math.round(10 * seconds) / 10;
4939
+ return Math.round(1000 * Math.min(maximum / 1000, Math.max(minimum / 1000, stepped)));
4940
+ }
4597
4941
  function PresentationTransitionPanel({ slideId, transition, editable, canApplyToAll, onChange, onApplyToAll }) {
4598
4942
  const update = (patch)=>{
4599
4943
  if (transition) onChange({
@@ -4891,6 +5235,10 @@ const presentationRibbonTabs = [
4891
5235
  id: 'transitions',
4892
5236
  label: '切换'
4893
5237
  },
5238
+ {
5239
+ id: 'animations',
5240
+ label: '动画'
5241
+ },
4894
5242
  {
4895
5243
  id: 'slideshow',
4896
5244
  label: '幻灯片放映',
@@ -4949,6 +5297,7 @@ function PresentationToolbar({ selectedSlide, selectedElement, selectedUnitCount
4949
5297
  const officeDialog = useOfficeDialog();
4950
5298
  const [fontSizeDraft, setFontSizeDraft] = useState(()=>selectedElement ? String(selectedElement.fontSize) : '');
4951
5299
  const fontFamilyValue = presentationFontFamilyValue(selectedElement?.fontFamily);
5300
+ const selectedAnimation = workSlideAnimationForElement(selectedSlide, selectedElement?.id);
4952
5301
  useEffect(()=>{
4953
5302
  setFontSizeDraft(selectedElement ? String(selectedElement.fontSize) : '');
4954
5303
  }, [
@@ -5363,6 +5712,16 @@ function PresentationToolbar({ selectedSlide, selectedElement, selectedUnitCount
5363
5712
  onChange: commands.setTransition,
5364
5713
  onApplyToAll: commands.applyTransitionToAll
5365
5714
  }),
5715
+ animations: /*#__PURE__*/ jsx(PresentationAnimationPanel, {
5716
+ animation: selectedAnimation,
5717
+ canMove: can.moveEntranceAnimation,
5718
+ canPreview: can.previewAnimations(),
5719
+ editable: can.setEntranceAnimation(selectedAnimation?.effect),
5720
+ onMove: commands.moveEntranceAnimation,
5721
+ onPreview: commands.previewAnimations,
5722
+ onSetEffect: commands.setEntranceAnimation,
5723
+ onUpdate: commands.updateEntranceAnimation
5724
+ }),
5366
5725
  slideshow: /*#__PURE__*/ jsxs(WorkOfficeRibbonGroup, {
5367
5726
  label: "开始放映",
5368
5727
  children: [
@@ -6502,7 +6861,7 @@ function usePresentationClipboard({ content, mode, targetId, selectedSlide, sele
6502
6861
  const selectedElement = selectedElements.at(-1) ?? null;
6503
6862
  const copySelection = useCallback(()=>{
6504
6863
  if (selectedElements.length) {
6505
- copyPresentationElements(selectedElements);
6864
+ copyPresentationElements(selectedElements, 'slide' === mode ? selectedSlide?.animations : void 0);
6506
6865
  showToast(selectedElements.length > 1 ? `已复制 ${selectedElements.length} 个对象` : '已复制演示元素', 'success');
6507
6866
  return true;
6508
6867
  }
@@ -6518,7 +6877,7 @@ function usePresentationClipboard({ content, mode, targetId, selectedSlide, sele
6518
6877
  const deleteSelectedElement = useCallback(()=>{
6519
6878
  if (!selectedElements.length || !targetId) return false;
6520
6879
  const selectedIds = new Set(selectedElements.map((element)=>element.id));
6521
- const next = updateTargetElements(content, mode, targetId, (elements)=>elements.filter((element)=>!selectedIds.has(element.id)));
6880
+ const next = updateTargetElements(content, mode, targetId, (elements)=>elements.filter((element)=>!selectedIds.has(element.id)), selectedIds);
6522
6881
  if (!next) return false;
6523
6882
  onChange(next);
6524
6883
  onSelectElements([]);
@@ -6533,7 +6892,7 @@ function usePresentationClipboard({ content, mode, targetId, selectedSlide, sele
6533
6892
  ]);
6534
6893
  const cutSelection = useCallback(()=>{
6535
6894
  if (selectedElements.length) {
6536
- copyPresentationElements(selectedElements);
6895
+ copyPresentationElements(selectedElements, 'slide' === mode ? selectedSlide?.animations : void 0);
6537
6896
  if (!deleteSelectedElement()) return false;
6538
6897
  showToast(selectedElements.length > 1 ? `已剪切 ${selectedElements.length} 个对象` : '已剪切演示元素', 'success');
6539
6898
  return true;
@@ -6572,17 +6931,25 @@ function usePresentationClipboard({ content, mode, targetId, selectedSlide, sele
6572
6931
  }
6573
6932
  if ('element' === clipboard.payload.kind || 'elements' === clipboard.payload.kind) {
6574
6933
  if (!targetId) return false;
6575
- const pasted = 'element' === clipboard.payload.kind ? [
6576
- clonePresentationElementForPaste(clipboard.payload.element, clipboard.offset)
6577
- ] : clonePresentationElementsForPaste(clipboard.payload.elements, clipboard.offset);
6934
+ const sourceElements = 'element' === clipboard.payload.kind ? [
6935
+ clipboard.payload.element
6936
+ ] : clipboard.payload.elements;
6937
+ const sourceAnimations = 'element' === clipboard.payload.kind ? clipboard.payload.animation ? [
6938
+ clipboard.payload.animation
6939
+ ] : [] : clipboard.payload.animations ?? [];
6940
+ const pasted = clonePresentationElementsAndAnimationsForPaste(sourceElements, sourceAnimations, clipboard.offset);
6941
+ if ('slide' === mode && !canAppendSlideAnimations(content, targetId, pasted.animations)) {
6942
+ showToast('对象动画数量已达到每张幻灯片 256 条的上限。', 'info');
6943
+ return true;
6944
+ }
6578
6945
  const next = updateTargetElements(content, mode, targetId, (elements)=>[
6579
6946
  ...elements,
6580
- ...pasted
6581
- ]);
6947
+ ...pasted.elements
6948
+ ], void 0, pasted.animations);
6582
6949
  if (!next) return false;
6583
6950
  onChange(next);
6584
- onSelectElements(pasted.map((element)=>element.id));
6585
- showToast(pasted.length > 1 ? `已粘贴 ${pasted.length} 个对象` : '已粘贴演示元素', 'success');
6951
+ onSelectElements(pasted.elements.map((element)=>element.id));
6952
+ showToast(pasted.elements.length > 1 ? `已粘贴 ${pasted.elements.length} 个对象` : '已粘贴演示元素', 'success');
6586
6953
  return true;
6587
6954
  }
6588
6955
  if ('slide' !== mode || !selectedSlide) {
@@ -6614,15 +6981,19 @@ function usePresentationClipboard({ content, mode, targetId, selectedSlide, sele
6614
6981
  ]);
6615
6982
  const duplicateSelection = useCallback(()=>{
6616
6983
  if (selectedElements.length && targetId) {
6617
- const copies = clonePresentationElementsForPaste(selectedElements, 5);
6984
+ const copies = clonePresentationElementsAndAnimationsForPaste(selectedElements, 'slide' === mode ? selectedSlide?.animations ?? [] : [], 5);
6985
+ if ('slide' === mode && !canAppendSlideAnimations(content, targetId, copies.animations)) {
6986
+ showToast('对象动画数量已达到每张幻灯片 256 条的上限。', 'info');
6987
+ return true;
6988
+ }
6618
6989
  const next = updateTargetElements(content, mode, targetId, (elements)=>[
6619
6990
  ...elements,
6620
- ...copies
6621
- ]);
6991
+ ...copies.elements
6992
+ ], void 0, copies.animations);
6622
6993
  if (!next) return false;
6623
6994
  onChange(next);
6624
- onSelectElements(copies.map((element)=>element.id));
6625
- showToast(copies.length > 1 ? `已复制 ${copies.length} 个对象` : '已复制演示元素', 'success');
6995
+ onSelectElements(copies.elements.map((element)=>element.id));
6996
+ showToast(copies.elements.length > 1 ? `已复制 ${copies.elements.length} 个对象` : '已复制演示元素', 'success');
6626
6997
  return true;
6627
6998
  }
6628
6999
  if ('slide' !== mode || !selectedSlide) return false;
@@ -6709,15 +7080,15 @@ function usePresentationClipboard({ content, mode, targetId, selectedSlide, sele
6709
7080
  toggleUnderline
6710
7081
  };
6711
7082
  }
6712
- function updateTargetElements(content, mode, targetId, update) {
7083
+ function updateTargetElements(content, mode, targetId, update, removedElementIds, addedAnimations) {
6713
7084
  if ('slide' === mode) {
6714
7085
  if (!content.slides.some((slide)=>slide.id === targetId)) return null;
6715
7086
  return {
6716
7087
  ...content,
6717
- slides: content.slides.map((slide)=>slide.id === targetId ? {
7088
+ slides: content.slides.map((slide)=>slide.id === targetId ? appendWorkSlideAnimations(removeWorkSlideAnimationsForElements({
6718
7089
  ...slide,
6719
7090
  elements: update(use_presentation_clipboard_structuredCopy(slide.elements))
6720
- } : slide)
7091
+ }, removedElementIds ?? new Set()), addedAnimations) : slide)
6721
7092
  };
6722
7093
  }
6723
7094
  const normalized = withPresentationDesign(content);
@@ -6740,10 +7111,130 @@ function updateTargetElements(content, mode, targetId, update) {
6740
7111
  } : master)
6741
7112
  };
6742
7113
  }
7114
+ function canAppendSlideAnimations(content, slideId, animations) {
7115
+ if (!animations?.length) return true;
7116
+ const slide = content.slides.find((candidate)=>candidate.id === slideId);
7117
+ return Boolean(slide) && (slide?.animations?.length ?? 0) + animations.length <= 256;
7118
+ }
7119
+ function appendWorkSlideAnimations(slide, animations) {
7120
+ if (!animations?.length) return slide;
7121
+ return {
7122
+ ...slide,
7123
+ animations: [
7124
+ ...slide.animations ?? [],
7125
+ ...use_presentation_clipboard_structuredCopy(animations)
7126
+ ]
7127
+ };
7128
+ }
6743
7129
  function use_presentation_clipboard_structuredCopy(value) {
6744
7130
  if ('function' == typeof structuredClone) return structuredClone(value);
6745
7131
  return JSON.parse(JSON.stringify(value));
6746
7132
  }
7133
+ function usePresentationAnimationCommands({ content, onChange, selectedElementId, selectedSlide }) {
7134
+ const selectedElementExists = Boolean(selectedElementId && selectedSlide.elements.some((element)=>element.id === selectedElementId));
7135
+ const selectedAnimation = workSlideAnimationForElement(selectedSlide, selectedElementId);
7136
+ const setEntranceAnimation = useCallback((effect)=>{
7137
+ if (!selectedElementId || !selectedElementExists) return false;
7138
+ const current = workSlideAnimationForElement(selectedSlide, selectedElementId);
7139
+ if (!effect) {
7140
+ if (!current) return false;
7141
+ updateSlide(content, selectedSlide.id, (slide)=>{
7142
+ const animations = slide.animations?.filter((animation)=>animation.id !== current.id);
7143
+ return {
7144
+ ...slide,
7145
+ animations: animations?.length ? animations : void 0
7146
+ };
7147
+ }, onChange);
7148
+ return true;
7149
+ }
7150
+ if (current?.effect === effect) return false;
7151
+ if (!current && (selectedSlide.animations?.length ?? 0) >= 256) return false;
7152
+ updateSlide(content, selectedSlide.id, (slide)=>{
7153
+ const next = createWorkSlideAnimation(selectedElementId, effect, current);
7154
+ const animations = [
7155
+ ...slide.animations ?? []
7156
+ ];
7157
+ const index = current ? animations.findIndex((animation)=>animation.id === current.id) : -1;
7158
+ if (index >= 0) animations[index] = next;
7159
+ else animations.push(next);
7160
+ return {
7161
+ ...slide,
7162
+ animations
7163
+ };
7164
+ }, onChange);
7165
+ return true;
7166
+ }, [
7167
+ content,
7168
+ onChange,
7169
+ selectedElementExists,
7170
+ selectedElementId,
7171
+ selectedSlide
7172
+ ]);
7173
+ const updateEntranceAnimation = useCallback((patch)=>{
7174
+ if (!selectedAnimation) return false;
7175
+ const next = normalizeWorkSlideAnimation({
7176
+ ...selectedAnimation,
7177
+ ...patch,
7178
+ id: selectedAnimation.id,
7179
+ elementId: selectedAnimation.elementId
7180
+ });
7181
+ if (animationsEqual(selectedAnimation, next)) return false;
7182
+ updateSlide(content, selectedSlide.id, (slide)=>({
7183
+ ...slide,
7184
+ animations: slide.animations?.map((animation)=>animation.id === selectedAnimation.id ? next : animation)
7185
+ }), onChange);
7186
+ return true;
7187
+ }, [
7188
+ content,
7189
+ onChange,
7190
+ selectedAnimation,
7191
+ selectedSlide.id
7192
+ ]);
7193
+ const canMoveEntranceAnimation = useCallback((direction)=>{
7194
+ const index = workSlideAnimationIndex(selectedSlide, selectedAnimation?.id);
7195
+ const target = index + direction;
7196
+ return index >= 0 && target >= 0 && target < (selectedSlide.animations?.length ?? 0);
7197
+ }, [
7198
+ selectedAnimation?.id,
7199
+ selectedSlide
7200
+ ]);
7201
+ const moveEntranceAnimation = useCallback((direction)=>{
7202
+ if (!selectedAnimation || !canMoveEntranceAnimation(direction)) return false;
7203
+ updateSlide(content, selectedSlide.id, (slide)=>{
7204
+ const animations = [
7205
+ ...slide.animations ?? []
7206
+ ];
7207
+ const index = animations.findIndex((animation)=>animation.id === selectedAnimation.id);
7208
+ const target = index + direction;
7209
+ [animations[index], animations[target]] = [
7210
+ animations[target],
7211
+ animations[index]
7212
+ ];
7213
+ return {
7214
+ ...slide,
7215
+ animations
7216
+ };
7217
+ }, onChange);
7218
+ return true;
7219
+ }, [
7220
+ canMoveEntranceAnimation,
7221
+ content,
7222
+ onChange,
7223
+ selectedAnimation,
7224
+ selectedSlide.id
7225
+ ]);
7226
+ return {
7227
+ canMoveEntranceAnimation,
7228
+ canSetEntranceAnimation: selectedElementExists,
7229
+ canUpdateEntranceAnimation: Boolean(selectedAnimation),
7230
+ moveEntranceAnimation,
7231
+ setEntranceAnimation,
7232
+ updateEntranceAnimation
7233
+ };
7234
+ }
7235
+ function animationsEqual(left, right) {
7236
+ return left.id === right.id && left.elementId === right.elementId && left.effect === right.effect && left.trigger === right.trigger && left.durationMs === right.durationMs && left.delayMs === right.delayMs && left.direction === right.direction;
7237
+ }
6747
7238
  function usePresentationDesignCommands({ activeElements, activeTargetId, designContent, designMode, designOpen, onChange, onClearSelection, onCloseComments, onEditElement, onSetDesignMode, onSetDesignOpen, selectedLayout, selectedMaster, selectedSlide }) {
6748
7239
  const updateLayout = useCallback((layoutId, update)=>{
6749
7240
  onChange({
@@ -8142,6 +8633,12 @@ function PresentationEditingSurface({ initialSlide, autoFocus = true, collaborat
8142
8633
  onSelectSlide: setSelectedSlideId,
8143
8634
  selectedSlide
8144
8635
  });
8636
+ const presentationAnimations = usePresentationAnimationCommands({
8637
+ content,
8638
+ onChange,
8639
+ selectedElementId: 'slide' === designMode && singleSelectedElement ? singleSelectedElement.id : void 0,
8640
+ selectedSlide
8641
+ });
8145
8642
  const presentationElements = usePresentationElementCommands({
8146
8643
  activeElements,
8147
8644
  activeTargetId,
@@ -8274,8 +8771,30 @@ function PresentationEditingSurface({ initialSlide, autoFocus = true, collaborat
8274
8771
  selectedTextEditor,
8275
8772
  toolbarSelectedElement
8276
8773
  ]);
8774
+ const startPresentationSlideshow = useCallback((source)=>{
8775
+ const currentIndex = content.slides.findIndex((slide)=>slide.id === selectedSlide.id);
8776
+ const activeElement = document.activeElement;
8777
+ slideshowReturnFocusRef.current = activeElement instanceof HTMLElement && activeElement !== document.body ? activeElement : document.querySelector(`[data-presentation-slideshow-source="${source}"]`);
8778
+ setAgentMenu(null);
8779
+ setSlideshowStartIndex('current' === source ? Math.max(0, currentIndex) : 0);
8780
+ onStartSlideshow?.();
8781
+ }, [
8782
+ content.slides,
8783
+ onStartSlideshow,
8784
+ selectedSlide.id
8785
+ ]);
8277
8786
  const presentationExtensions = useMemo(createPresentationEditorExtensions, []);
8278
8787
  const presentationEditor = useOfficeEditorRuntime({
8788
+ animations: {
8789
+ canMoveEntranceAnimation: (direction)=>'slide' === designMode && presentationAnimations.canMoveEntranceAnimation(direction),
8790
+ canPreviewAnimations: 'slide' === designMode && Boolean(selectedSlide.animations?.length),
8791
+ canSetEntranceAnimation: 'slide' === designMode && presentationAnimations.canSetEntranceAnimation,
8792
+ canUpdateEntranceAnimation: 'slide' === designMode && presentationAnimations.canUpdateEntranceAnimation,
8793
+ moveEntranceAnimation: presentationAnimations.moveEntranceAnimation,
8794
+ previewAnimations: ()=>startPresentationSlideshow('current'),
8795
+ setEntranceAnimation: presentationAnimations.setEntranceAnimation,
8796
+ updateEntranceAnimation: presentationAnimations.updateEntranceAnimation
8797
+ },
8279
8798
  clipboard: {
8280
8799
  canCopySelection: selectedElements.length > 0 || 'slide' === designMode && Boolean(selectedSlide),
8281
8800
  canCutSelection: selectedElements.length > 0 || 'slide' === designMode && Boolean(selectedSlide),
@@ -8392,14 +8911,7 @@ function PresentationEditingSurface({ initialSlide, autoFocus = true, collaborat
8392
8911
  view: {
8393
8912
  canStartSlideshow: 'slide' === designMode && content.slides.length > 0,
8394
8913
  setViewMode,
8395
- startSlideshow: (source)=>{
8396
- const currentIndex = content.slides.findIndex((slide)=>slide.id === selectedSlide.id);
8397
- const activeElement = document.activeElement;
8398
- slideshowReturnFocusRef.current = activeElement instanceof HTMLElement && activeElement !== document.body ? activeElement : document.querySelector(`[data-presentation-slideshow-source="${source}"]`);
8399
- setAgentMenu(null);
8400
- setSlideshowStartIndex('current' === source ? Math.max(0, currentIndex) : 0);
8401
- onStartSlideshow?.();
8402
- },
8914
+ startSlideshow: startPresentationSlideshow,
8403
8915
  toggleDesign: presentationDesign.toggleDesignPanel
8404
8916
  }
8405
8917
  }, presentationExtensions);