@a3s-lab/office 0.33.0 → 0.35.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 (30) hide show
  1. package/README.md +23 -19
  2. package/dist/0~5809.js +123 -0
  3. package/dist/{0~7614.js → 0~7828.js} +376 -5
  4. package/dist/0~pdf-viewer.js +5 -1
  5. package/dist/0~presentation-editor.js +597 -71
  6. package/dist/0~spreadsheet-editor.js +166 -18
  7. package/dist/0~work-pptx-export.js +50 -14
  8. package/dist/0~work-pptx-import.js +21 -9
  9. package/dist/4121.js +122 -1
  10. package/dist/4560.js +33 -0
  11. package/dist/internal/features/work/editors/presentation-animation-panel.d.ts +11 -0
  12. package/dist/internal/features/work/editors/presentation-command-types.d.ts +16 -1
  13. package/dist/internal/features/work/editors/presentation-editor-focus.d.ts +2 -0
  14. package/dist/internal/features/work/editors/presentation-presenter-view.d.ts +2 -1
  15. package/dist/internal/features/work/editors/presentation-slide-canvas.d.ts +11 -3
  16. package/dist/internal/features/work/editors/presentation-text-editor.d.ts +7 -0
  17. package/dist/internal/features/work/editors/spreadsheet-command-controller.d.ts +10 -0
  18. package/dist/internal/features/work/editors/spreadsheet-command-selection.d.ts +22 -1
  19. package/dist/internal/features/work/editors/use-presentation-animation-commands.d.ts +15 -0
  20. package/dist/internal/features/work/editors/use-presentation-selection.d.ts +1 -1
  21. package/dist/internal/features/work/work-pptx-animation.d.ts +13 -0
  22. package/dist/internal/features/work/work-pptx-groups.d.ts +4 -0
  23. package/dist/internal/features/work/work-presentation-animation-constraints.d.ts +4 -0
  24. package/dist/internal/features/work/work-presentation-animation.d.ts +20 -0
  25. package/dist/internal/features/work/work-presentation-clipboard.d.ts +10 -3
  26. package/dist/internal/features/work/work-types.d.ts +17 -0
  27. package/dist/office-kernel.wasm +0 -0
  28. package/dist/styles.css +123 -0
  29. package/package.json +19 -13
  30. 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";
@@ -19,9 +19,9 @@ import { officePresenceColorStyle, office_text_field_OfficeTextArea, stepOfficeZ
19
19
  import { useOfficeTaskPaneEscape, normalizeOfficeFontFamily, OfficeCheckbox, CommittedOfficeNumberField, useOfficeTaskPaneModal, officeFontFamilyLabel, useOfficeDialog, officeFontFamilies, CollectionState, createOfficeKernelClient, OfficeNumberField, handleOfficeTaskPaneKeyDown } from "./0~7048.js";
20
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: ()=>({
@@ -2393,6 +2425,9 @@ function PlaceholderButtons({ onAdd }) {
2393
2425
  });
2394
2426
  }
2395
2427
  const PRESENTATION_WORKSPACE_FOCUS_RETRY_FRAMES = 6;
2428
+ function presentationInitialEditingElementId(elements) {
2429
+ return elements.find((element)=>element.placeholder?.type === 'title' && presentationElementCanEditContent(element) && !element.text.trim() && !element.textRuns?.some((run)=>run.text.trim()))?.id ?? null;
2430
+ }
2396
2431
  function restorePresentationWorkspaceFocus(root, getState, focusOrigin = document.activeElement) {
2397
2432
  if (!root) return;
2398
2433
  const commandTrigger = focusOrigin;
@@ -2443,17 +2478,20 @@ function presentationCommandsWithObjectFocus(commands, restoreFocus) {
2443
2478
  distributeElements: afterSuccessfulCommand(commands.distributeElements),
2444
2479
  duplicateSlide: afterSuccessfulCommand(commands.duplicateSlide),
2445
2480
  groupElements: afterSuccessfulCommand(commands.groupElements),
2481
+ moveEntranceAnimation: afterSuccessfulCommand(commands.moveEntranceAnimation),
2446
2482
  pasteSelection: afterSuccessfulCommand(commands.pasteSelection),
2447
2483
  redo: afterSuccessfulCommand(commands.redo),
2448
2484
  reorderElement: afterSuccessfulCommand(commands.reorderElement),
2449
2485
  setTransition: afterSuccessfulCommand(commands.setTransition),
2486
+ setEntranceAnimation: afterSuccessfulCommand(commands.setEntranceAnimation),
2450
2487
  setViewMode: afterSuccessfulCommand(commands.setViewMode),
2451
2488
  toggleBold: afterSuccessfulCommand(commands.toggleBold),
2452
2489
  toggleItalic: afterSuccessfulCommand(commands.toggleItalic),
2453
2490
  toggleUnderline: afterSuccessfulCommand(commands.toggleUnderline),
2454
2491
  undo: afterSuccessfulCommand(commands.undo),
2455
2492
  ungroupElements: afterSuccessfulCommand(commands.ungroupElements),
2456
- updateElement: afterSuccessfulCommand(commands.updateElement)
2493
+ updateElement: afterSuccessfulCommand(commands.updateElement),
2494
+ updateEntranceAnimation: afterSuccessfulCommand(commands.updateEntranceAnimation)
2457
2495
  };
2458
2496
  }
2459
2497
  function presentationChartAnalysisBounds(chart, xValues) {
@@ -3572,7 +3610,7 @@ function drawRadarChart(context, chart, sourceRect) {
3572
3610
  });
3573
3611
  }
3574
3612
  }
3575
- function SlideCanvas({ content, designContent, slide, interactive, aspectRatio, showPlaceholders = false }) {
3613
+ function SlideCanvas({ content, designContent, slide, interactive, aspectRatio, animationCueIndex, showPlaceholders = false }) {
3576
3614
  const view = designContent ? presentationSlideViewFromDesign(designContent, slide) : content ? presentationSlideView(content, slide) : void 0;
3577
3615
  const elements = [
3578
3616
  ...view?.inheritedElements.map((element)=>({
@@ -3584,6 +3622,7 @@ function SlideCanvas({ content, designContent, slide, interactive, aspectRatio,
3584
3622
  origin: 'slide'
3585
3623
  }))
3586
3624
  ];
3625
+ const animationPlayback = void 0 === animationCueIndex ? void 0 : slideAnimationPlayback(slide.animations, animationCueIndex);
3587
3626
  return /*#__PURE__*/ jsx("span", {
3588
3627
  className: `work-slide-canvas ${interactive ? 'interactive' : ''}`,
3589
3628
  style: {
@@ -3593,16 +3632,23 @@ function SlideCanvas({ content, designContent, slide, interactive, aspectRatio,
3593
3632
  children: elements.map(({ element, origin })=>/*#__PURE__*/ jsx(SlideElementPreview, {
3594
3633
  element: element,
3595
3634
  origin: origin,
3596
- showPlaceholder: showPlaceholders
3635
+ showPlaceholder: showPlaceholders,
3636
+ animationPlayback: 'slide' === origin ? animationPlayback?.get(element.id) : void 0
3597
3637
  }, `${origin}:${element.id}`))
3598
3638
  });
3599
3639
  }
3600
- function SlideElementPreview({ element, origin, showPlaceholder = false }) {
3640
+ function SlideElementPreview({ animationPlayback, element, origin, showPlaceholder = false }) {
3601
3641
  const hasRichText = element.textRuns?.some((run)=>run.text.length > 0);
3602
3642
  return /*#__PURE__*/ jsx("span", {
3603
3643
  className: `work-slide-element ${element.type} ${origin} ${showPlaceholder && element.placeholder ? 'placeholder' : ''}`.trim(),
3644
+ "data-slide-preview-element-id": element.id,
3604
3645
  "data-slide-element-origin": origin,
3605
- style: slideElementStyle(element),
3646
+ "data-slide-animation-effect": animationPlayback?.animation.effect,
3647
+ "data-slide-animation-state": animationPlayback?.state,
3648
+ style: {
3649
+ ...slideElementStyle(element),
3650
+ ...slideElementAnimationStyle(animationPlayback)
3651
+ },
3606
3652
  children: 'image' === element.type && element.image ? /*#__PURE__*/ jsx("img", {
3607
3653
  src: element.image.dataUrl,
3608
3654
  alt: element.altText ?? element.image.name
@@ -3658,6 +3704,8 @@ function slideElementStyle(element) {
3658
3704
  borderRadius: 'ellipse' === element.shapeType ? '50%' : `${element.radius ?? 0}%`,
3659
3705
  clipPath: shapeClipPath,
3660
3706
  opacity: element.opacity,
3707
+ '--work-slide-element-opacity': element.opacity ?? 1,
3708
+ '--work-slide-element-rotation': `${element.rotation ?? 0}deg`,
3661
3709
  transform: element.rotation ? `rotate(${element.rotation}deg)` : void 0,
3662
3710
  transformOrigin: 'center',
3663
3711
  ...'line' === element.type ? {
@@ -3666,6 +3714,27 @@ function slideElementStyle(element) {
3666
3714
  } : {}
3667
3715
  };
3668
3716
  }
3717
+ function slideAnimationPlayback(animations, activeCueIndex) {
3718
+ const playback = new Map();
3719
+ for (const [cueIndex, cue] of workSlideAnimationCues(animations).entries())for (const item of cue.items)playback.set(item.animation.elementId, {
3720
+ animation: item.animation,
3721
+ startOffsetMs: item.startOffsetMs,
3722
+ state: cueIndex < activeCueIndex ? 'finished' : cueIndex === activeCueIndex ? 'playing' : 'pending'
3723
+ });
3724
+ return playback;
3725
+ }
3726
+ function slideElementAnimationStyle(playback) {
3727
+ if (!playback) return {};
3728
+ const direction = playback.animation.direction ?? 'left';
3729
+ const translateX = 'left' === direction ? '-18%' : 'right' === direction ? '18%' : '0';
3730
+ const translateY = 'up' === direction ? '-18%' : 'down' === direction ? '18%' : '0';
3731
+ return {
3732
+ '--work-slide-animation-delay': `${playback.startOffsetMs}ms`,
3733
+ '--work-slide-animation-duration': `${playback.animation.durationMs}ms`,
3734
+ '--work-slide-animation-translate-x': translateX,
3735
+ '--work-slide-animation-translate-y': translateY
3736
+ };
3737
+ }
3669
3738
  function slideTextStyle(element) {
3670
3739
  return {
3671
3740
  color: element.color,
@@ -3741,7 +3810,7 @@ function createPresentationTimerController(now = Date.now()) {
3741
3810
  runningSinceMilliseconds: now
3742
3811
  };
3743
3812
  }
3744
- function PresentationPresenterView({ content, slide, nextSlide, index, total, aspectRatio, timer }) {
3813
+ function PresentationPresenterView({ animationCueIndex, content, slide, nextSlide, index, total, aspectRatio, timer }) {
3745
3814
  const [, setTimerRevision] = useState(0);
3746
3815
  const now = Date.now();
3747
3816
  const running = null !== timer.runningSinceMilliseconds;
@@ -3839,6 +3908,7 @@ function PresentationPresenterView({ content, slide, nextSlide, index, total, as
3839
3908
  children: slide.name
3840
3909
  }),
3841
3910
  /*#__PURE__*/ jsx(SlideCanvas, {
3911
+ animationCueIndex: animationCueIndex,
3842
3912
  content: content,
3843
3913
  slide: slide,
3844
3914
  interactive: false,
@@ -3901,6 +3971,7 @@ function formatDuration(seconds) {
3901
3971
  }
3902
3972
  function PresentationPlayer({ autoFullscreen = false, content, initialIndex = 0, onExit }) {
3903
3973
  const [playback, setPlayback] = useState({
3974
+ animationCueIndex: initialAnimationCueIndex(content.slides[presentationPlaybackIndex(initialIndex, content.slides.length)]),
3904
3975
  index: presentationPlaybackIndex(initialIndex, content.slides.length),
3905
3976
  transitionKey: 0
3906
3977
  });
@@ -3931,12 +4002,52 @@ function PresentationPlayer({ autoFullscreen = false, content, initialIndex = 0,
3931
4002
  setPlayback((current)=>{
3932
4003
  const index = Math.min(Math.max(current.index + delta, 0), content.slides.length - 1);
3933
4004
  return index === current.index ? current : {
4005
+ animationCueIndex: initialAnimationCueIndex(content.slides[index]),
4006
+ index,
4007
+ transitionKey: current.transitionKey + 1
4008
+ };
4009
+ });
4010
+ }, [
4011
+ content.slides
4012
+ ]);
4013
+ const advance = useCallback(()=>{
4014
+ setPlayback((current)=>{
4015
+ const currentSlide = content.slides[current.index];
4016
+ const cueCount = workSlideAnimationCues(currentSlide?.animations).length;
4017
+ if (current.animationCueIndex < cueCount - 1) return {
4018
+ ...current,
4019
+ animationCueIndex: current.animationCueIndex + 1
4020
+ };
4021
+ const index = Math.min(current.index + 1, content.slides.length - 1);
4022
+ if (index === current.index) return current;
4023
+ return {
4024
+ animationCueIndex: initialAnimationCueIndex(content.slides[index]),
4025
+ index,
4026
+ transitionKey: current.transitionKey + 1
4027
+ };
4028
+ });
4029
+ }, [
4030
+ content.slides
4031
+ ]);
4032
+ const retreat = useCallback(()=>{
4033
+ setPlayback((current)=>{
4034
+ const currentSlide = content.slides[current.index];
4035
+ const initialCueIndex = initialAnimationCueIndex(currentSlide);
4036
+ if (current.animationCueIndex > initialCueIndex) return {
4037
+ ...current,
4038
+ animationCueIndex: current.animationCueIndex - 1
4039
+ };
4040
+ const index = Math.max(current.index - 1, 0);
4041
+ if (index === current.index) return current;
4042
+ const previousCues = workSlideAnimationCues(content.slides[index]?.animations);
4043
+ return {
4044
+ animationCueIndex: previousCues.length - 1,
3934
4045
  index,
3935
4046
  transitionKey: current.transitionKey + 1
3936
4047
  };
3937
4048
  });
3938
4049
  }, [
3939
- content.slides.length
4050
+ content.slides
3940
4051
  ]);
3941
4052
  useEffect(()=>{
3942
4053
  const onKeyDown = (event)=>{
@@ -3950,12 +4061,12 @@ function PresentationPlayer({ autoFullscreen = false, content, initialIndex = 0,
3950
4061
  if ((' ' === event.key || 'Spacebar' === event.key) && presentationTargetActivatesWithSpace(event.target)) return;
3951
4062
  if ('ArrowRight' === event.key || 'ArrowDown' === event.key || 'PageDown' === event.key || ' ' === event.key || 'Spacebar' === event.key) {
3952
4063
  event.preventDefault();
3953
- move(1);
4064
+ advance();
3954
4065
  return;
3955
4066
  }
3956
4067
  if ('ArrowLeft' === event.key || 'ArrowUp' === event.key || 'PageUp' === event.key) {
3957
4068
  event.preventDefault();
3958
- move(-1);
4069
+ retreat();
3959
4070
  return;
3960
4071
  }
3961
4072
  if ('Home' === event.key) {
@@ -3971,9 +4082,11 @@ function PresentationPlayer({ autoFullscreen = false, content, initialIndex = 0,
3971
4082
  window.addEventListener('keydown', onKeyDown);
3972
4083
  return ()=>window.removeEventListener('keydown', onKeyDown);
3973
4084
  }, [
4085
+ advance,
3974
4086
  completeExit,
3975
4087
  content.slides.length,
3976
- move
4088
+ move,
4089
+ retreat
3977
4090
  ]);
3978
4091
  useLayoutEffect(()=>{
3979
4092
  const player = playerRef.current;
@@ -4024,6 +4137,8 @@ function PresentationPlayer({ autoFullscreen = false, content, initialIndex = 0,
4024
4137
  if (!slide) return null;
4025
4138
  const aspectRatio = `${content.width ?? 13.333} / ${content.height ?? 7.5}`;
4026
4139
  const transition = slide.transition;
4140
+ const animationCues = workSlideAnimationCues(slide.animations);
4141
+ const hasPendingAnimation = playback.animationCueIndex < animationCues.length - 1;
4027
4142
  const transitionStyle = {
4028
4143
  '--work-slide-transition-duration': `${slideTransitionDurationMilliseconds(transition)}ms`
4029
4144
  };
@@ -4034,6 +4149,7 @@ function PresentationPlayer({ autoFullscreen = false, content, initialIndex = 0,
4034
4149
  tabIndex: -1,
4035
4150
  children: [
4036
4151
  presenter ? /*#__PURE__*/ jsx(PresentationPresenterView, {
4152
+ animationCueIndex: playback.animationCueIndex,
4037
4153
  content: content,
4038
4154
  slide: slide,
4039
4155
  nextSlide: content.slides[playback.index + 1],
@@ -4048,8 +4164,8 @@ function PresentationPlayer({ autoFullscreen = false, content, initialIndex = 0,
4048
4164
  type: "button",
4049
4165
  className: "work-presentation-player-advance",
4050
4166
  "aria-label": "单击换到下一张幻灯片",
4051
- disabled: transition?.advanceOnClick === false || playback.index === content.slides.length - 1,
4052
- onClick: ()=>move(1)
4167
+ disabled: !hasPendingAnimation && (transition?.advanceOnClick === false || playback.index === content.slides.length - 1),
4168
+ onClick: ()=>advance()
4053
4169
  }),
4054
4170
  /*#__PURE__*/ jsx("div", {
4055
4171
  "aria-live": "polite",
@@ -4061,6 +4177,7 @@ function PresentationPlayer({ autoFullscreen = false, content, initialIndex = 0,
4061
4177
  "data-transition-speed": transition?.speed ?? 'medium',
4062
4178
  style: transitionStyle,
4063
4179
  children: /*#__PURE__*/ jsx(SlideCanvas, {
4180
+ animationCueIndex: playback.animationCueIndex,
4064
4181
  content: content,
4065
4182
  slide: slide,
4066
4183
  interactive: false,
@@ -4075,8 +4192,8 @@ function PresentationPlayer({ autoFullscreen = false, content, initialIndex = 0,
4075
4192
  type: "button",
4076
4193
  "aria-label": "上一张",
4077
4194
  "aria-keyshortcuts": "ArrowLeft ArrowUp PageUp",
4078
- disabled: 0 === playback.index,
4079
- onClick: ()=>move(-1),
4195
+ disabled: 0 === playback.index && playback.animationCueIndex <= initialAnimationCueIndex(slide),
4196
+ onClick: retreat,
4080
4197
  children: /*#__PURE__*/ jsx(ChevronLeft, {
4081
4198
  size: 18
4082
4199
  })
@@ -4092,8 +4209,8 @@ function PresentationPlayer({ autoFullscreen = false, content, initialIndex = 0,
4092
4209
  type: "button",
4093
4210
  "aria-label": "下一张",
4094
4211
  "aria-keyshortcuts": "ArrowRight ArrowDown PageDown Space",
4095
- disabled: playback.index === content.slides.length - 1,
4096
- onClick: ()=>move(1),
4212
+ disabled: !hasPendingAnimation && playback.index === content.slides.length - 1,
4213
+ onClick: advance,
4097
4214
  children: /*#__PURE__*/ jsx(ChevronRight, {
4098
4215
  size: 18
4099
4216
  })
@@ -4146,6 +4263,9 @@ function presentationPlaybackIndex(index, slideCount) {
4146
4263
  if (slideCount <= 1) return 0;
4147
4264
  return Math.min(slideCount - 1, Math.max(0, Math.trunc(index)));
4148
4265
  }
4266
+ function initialAnimationCueIndex(slide) {
4267
+ return initialWorkSlideAnimationCueIndex(workSlideAnimationCues(slide?.animations));
4268
+ }
4149
4269
  async function requestPresentationFullscreen(element) {
4150
4270
  if (!element.requestFullscreen) return false;
4151
4271
  try {
@@ -4239,6 +4359,7 @@ function PresentationTextEditor({ autoFocus = false, element, onChange, onEditor
4239
4359
  const onSelectionChangeRef = useRef(onSelectionChange);
4240
4360
  const appliedSignatureRef = useRef(presentationTextElementSignature(element));
4241
4361
  const initialContentRef = useRef(presentationTextElementHtml(element));
4362
+ const initialFocusTargetRef = useRef("u" > typeof document && document.activeElement instanceof HTMLElement ? document.activeElement : null);
4242
4363
  elementRef.current = element;
4243
4364
  onChangeRef.current = onChange;
4244
4365
  onEditorChangeRef.current = onEditorChange;
@@ -4293,7 +4414,8 @@ function PresentationTextEditor({ autoFocus = false, element, onChange, onEditor
4293
4414
  useEffect(()=>{
4294
4415
  if (!autoFocus || !editor || editor.isDestroyed) return;
4295
4416
  const frame = window.requestAnimationFrame(()=>{
4296
- if (!editor.isDestroyed) editor.commands.focus('end');
4417
+ if (editor.isDestroyed || !presentationFocusOwnerIsUnchanged(initialFocusTargetRef.current)) return;
4418
+ editor.commands.focus('end');
4297
4419
  });
4298
4420
  return ()=>window.cancelAnimationFrame(frame);
4299
4421
  }, [
@@ -4321,6 +4443,12 @@ function PresentationTextEditor({ autoFocus = false, element, onChange, onEditor
4321
4443
  style: presentationTextBaseStyle(element)
4322
4444
  });
4323
4445
  }
4446
+ function presentationFocusOwnerIsUnchanged(initialFocusTarget) {
4447
+ if ("u" < typeof document) return true;
4448
+ const currentFocusTarget = document.activeElement;
4449
+ if (!currentFocusTarget || currentFocusTarget === document.body) return true;
4450
+ return !initialFocusTarget || currentFocusTarget === initialFocusTarget;
4451
+ }
4324
4452
  function createPresentationTextEditorExtensions(placeholder = '输入文字') {
4325
4453
  return [
4326
4454
  starter_kit.configure({
@@ -4594,6 +4722,233 @@ function commonRunValue(runs, getValue, fallback) {
4594
4722
  const values = runs.map((run)=>getValue(run) ?? fallback);
4595
4723
  return values.every((value)=>value === values[0]) ? values[0] : fallback;
4596
4724
  }
4725
+ function PresentationAnimationPanel({ animation, canMove, canPreview, editable, onMove, onPreview, onSetEffect, onUpdate }) {
4726
+ return /*#__PURE__*/ jsxs(Fragment, {
4727
+ children: [
4728
+ /*#__PURE__*/ jsx(WorkOfficeRibbonGroup, {
4729
+ label: "入场动画",
4730
+ children: /*#__PURE__*/ jsxs("fieldset", {
4731
+ className: "work-presentation-animation-options",
4732
+ children: [
4733
+ /*#__PURE__*/ jsx("legend", {
4734
+ className: "sr-only",
4735
+ children: "对象入场动画设置"
4736
+ }),
4737
+ /*#__PURE__*/ jsxs("div", {
4738
+ className: "work-office-field effect",
4739
+ children: [
4740
+ /*#__PURE__*/ jsx("span", {
4741
+ children: "效果"
4742
+ }),
4743
+ /*#__PURE__*/ jsx(OfficeSelect, {
4744
+ ariaLabel: "对象入场动画效果",
4745
+ disabled: !editable,
4746
+ value: animation?.effect ?? 'none',
4747
+ options: [
4748
+ {
4749
+ value: 'none',
4750
+ label: '无'
4751
+ },
4752
+ {
4753
+ value: 'appear',
4754
+ label: '出现'
4755
+ },
4756
+ {
4757
+ value: 'fade',
4758
+ label: '淡入'
4759
+ },
4760
+ {
4761
+ value: 'fly-in',
4762
+ label: '飞入'
4763
+ },
4764
+ {
4765
+ value: 'zoom',
4766
+ label: '缩放'
4767
+ }
4768
+ ],
4769
+ onValueChange: (effect)=>onSetEffect('none' === effect ? void 0 : effect)
4770
+ })
4771
+ ]
4772
+ }),
4773
+ /*#__PURE__*/ jsxs("div", {
4774
+ className: "work-office-field trigger",
4775
+ children: [
4776
+ /*#__PURE__*/ jsx("span", {
4777
+ children: "开始"
4778
+ }),
4779
+ /*#__PURE__*/ jsx(OfficeSelect, {
4780
+ ariaLabel: "对象入场动画触发方式",
4781
+ disabled: !animation,
4782
+ value: animation?.trigger ?? 'on-click',
4783
+ options: [
4784
+ {
4785
+ value: 'on-click',
4786
+ label: '单击时'
4787
+ },
4788
+ {
4789
+ value: 'with-previous',
4790
+ label: '与上一动画同时'
4791
+ },
4792
+ {
4793
+ value: 'after-previous',
4794
+ label: '上一动画之后'
4795
+ }
4796
+ ],
4797
+ onValueChange: (trigger)=>onUpdate({
4798
+ trigger: trigger
4799
+ })
4800
+ })
4801
+ ]
4802
+ }),
4803
+ animation?.effect === 'fly-in' && /*#__PURE__*/ jsxs("div", {
4804
+ className: "work-office-field direction",
4805
+ children: [
4806
+ /*#__PURE__*/ jsx("span", {
4807
+ children: "方向"
4808
+ }),
4809
+ /*#__PURE__*/ jsx(OfficeSelect, {
4810
+ ariaLabel: "对象飞入方向",
4811
+ value: animation.direction ?? 'left',
4812
+ options: [
4813
+ {
4814
+ value: 'left',
4815
+ label: '从左侧'
4816
+ },
4817
+ {
4818
+ value: 'right',
4819
+ label: '从右侧'
4820
+ },
4821
+ {
4822
+ value: 'up',
4823
+ label: '从上方'
4824
+ },
4825
+ {
4826
+ value: 'down',
4827
+ label: '从下方'
4828
+ }
4829
+ ],
4830
+ onValueChange: (direction)=>onUpdate({
4831
+ direction: direction
4832
+ })
4833
+ })
4834
+ ]
4835
+ })
4836
+ ]
4837
+ })
4838
+ }),
4839
+ /*#__PURE__*/ jsx(WorkOfficeRibbonGroup, {
4840
+ label: "动画计时",
4841
+ children: /*#__PURE__*/ jsxs("div", {
4842
+ className: "work-presentation-animation-timing",
4843
+ children: [
4844
+ /*#__PURE__*/ jsx(AnimationTimingField, {
4845
+ animationId: animation?.id,
4846
+ ariaLabel: "对象入场动画持续秒数",
4847
+ disabled: !animation,
4848
+ label: "持续",
4849
+ maximumMs: 60000,
4850
+ minimumMs: 100,
4851
+ valueMs: animation?.durationMs ?? 500,
4852
+ onCommit: (durationMs)=>onUpdate({
4853
+ durationMs
4854
+ })
4855
+ }),
4856
+ /*#__PURE__*/ jsx(AnimationTimingField, {
4857
+ animationId: animation?.id,
4858
+ ariaLabel: "对象入场动画延迟秒数",
4859
+ disabled: !animation,
4860
+ label: "延迟",
4861
+ maximumMs: 60000,
4862
+ minimumMs: 0,
4863
+ valueMs: animation?.delayMs ?? 0,
4864
+ onCommit: (delayMs)=>onUpdate({
4865
+ delayMs
4866
+ })
4867
+ })
4868
+ ]
4869
+ })
4870
+ }),
4871
+ /*#__PURE__*/ jsxs(WorkOfficeRibbonGroup, {
4872
+ label: "动画顺序",
4873
+ children: [
4874
+ /*#__PURE__*/ jsx(WorkOfficeRibbonButton, {
4875
+ label: "提前对象动画",
4876
+ visibleLabel: "提前",
4877
+ disabled: !canMove(-1),
4878
+ onClick: ()=>onMove(-1),
4879
+ children: /*#__PURE__*/ jsx(ArrowUp, {
4880
+ size: 19
4881
+ })
4882
+ }),
4883
+ /*#__PURE__*/ jsx(WorkOfficeRibbonButton, {
4884
+ label: "推后对象动画",
4885
+ visibleLabel: "推后",
4886
+ disabled: !canMove(1),
4887
+ onClick: ()=>onMove(1),
4888
+ children: /*#__PURE__*/ jsx(ArrowDown, {
4889
+ size: 19
4890
+ })
4891
+ })
4892
+ ]
4893
+ }),
4894
+ /*#__PURE__*/ jsx(WorkOfficeRibbonGroup, {
4895
+ label: "动画预览",
4896
+ children: /*#__PURE__*/ jsx(WorkOfficeRibbonButton, {
4897
+ label: "预览当前幻灯片动画",
4898
+ visibleLabel: "预览",
4899
+ disabled: !canPreview,
4900
+ onClick: onPreview,
4901
+ children: /*#__PURE__*/ jsx(Play, {
4902
+ size: 19
4903
+ })
4904
+ })
4905
+ })
4906
+ ]
4907
+ });
4908
+ }
4909
+ function AnimationTimingField({ animationId, ariaLabel, disabled, label, maximumMs, minimumMs, onCommit, valueMs }) {
4910
+ const canonical = animationSecondsDraft(valueMs);
4911
+ const [draft, setDraft] = useState(canonical);
4912
+ useEffect(()=>setDraft(canonical), [
4913
+ animationId,
4914
+ canonical
4915
+ ]);
4916
+ const commit = (value)=>{
4917
+ const next = normalizedAnimationMilliseconds(value, valueMs, minimumMs, maximumMs);
4918
+ setDraft(animationSecondsDraft(next));
4919
+ if (next !== valueMs) onCommit(next);
4920
+ };
4921
+ return /*#__PURE__*/ jsxs("div", {
4922
+ className: "work-office-field seconds",
4923
+ children: [
4924
+ /*#__PURE__*/ jsx("span", {
4925
+ children: label
4926
+ }),
4927
+ /*#__PURE__*/ jsx(OfficeNumberField, {
4928
+ ariaLabel: ariaLabel,
4929
+ disabled: disabled,
4930
+ min: minimumMs / 1000,
4931
+ max: maximumMs / 1000,
4932
+ step: 0.1,
4933
+ value: draft,
4934
+ escapeConsumer: draft !== canonical,
4935
+ onValueChange: setDraft,
4936
+ onCommit: commit,
4937
+ onCancel: ()=>setDraft(canonical)
4938
+ })
4939
+ ]
4940
+ });
4941
+ }
4942
+ function animationSecondsDraft(valueMs) {
4943
+ return String(valueMs / 1000);
4944
+ }
4945
+ function normalizedAnimationMilliseconds(value, current, minimum, maximum) {
4946
+ if (!value.trim()) return current;
4947
+ const seconds = Number(value);
4948
+ if (!Number.isFinite(seconds)) return current;
4949
+ const stepped = Math.round(10 * seconds) / 10;
4950
+ return Math.round(1000 * Math.min(maximum / 1000, Math.max(minimum / 1000, stepped)));
4951
+ }
4597
4952
  function PresentationTransitionPanel({ slideId, transition, editable, canApplyToAll, onChange, onApplyToAll }) {
4598
4953
  const update = (patch)=>{
4599
4954
  if (transition) onChange({
@@ -4891,6 +5246,10 @@ const presentationRibbonTabs = [
4891
5246
  id: 'transitions',
4892
5247
  label: '切换'
4893
5248
  },
5249
+ {
5250
+ id: 'animations',
5251
+ label: '动画'
5252
+ },
4894
5253
  {
4895
5254
  id: 'slideshow',
4896
5255
  label: '幻灯片放映',
@@ -4949,6 +5308,7 @@ function PresentationToolbar({ selectedSlide, selectedElement, selectedUnitCount
4949
5308
  const officeDialog = useOfficeDialog();
4950
5309
  const [fontSizeDraft, setFontSizeDraft] = useState(()=>selectedElement ? String(selectedElement.fontSize) : '');
4951
5310
  const fontFamilyValue = presentationFontFamilyValue(selectedElement?.fontFamily);
5311
+ const selectedAnimation = workSlideAnimationForElement(selectedSlide, selectedElement?.id);
4952
5312
  useEffect(()=>{
4953
5313
  setFontSizeDraft(selectedElement ? String(selectedElement.fontSize) : '');
4954
5314
  }, [
@@ -5363,6 +5723,16 @@ function PresentationToolbar({ selectedSlide, selectedElement, selectedUnitCount
5363
5723
  onChange: commands.setTransition,
5364
5724
  onApplyToAll: commands.applyTransitionToAll
5365
5725
  }),
5726
+ animations: /*#__PURE__*/ jsx(PresentationAnimationPanel, {
5727
+ animation: selectedAnimation,
5728
+ canMove: can.moveEntranceAnimation,
5729
+ canPreview: can.previewAnimations(),
5730
+ editable: can.setEntranceAnimation(selectedAnimation?.effect),
5731
+ onMove: commands.moveEntranceAnimation,
5732
+ onPreview: commands.previewAnimations,
5733
+ onSetEffect: commands.setEntranceAnimation,
5734
+ onUpdate: commands.updateEntranceAnimation
5735
+ }),
5366
5736
  slideshow: /*#__PURE__*/ jsxs(WorkOfficeRibbonGroup, {
5367
5737
  label: "开始放映",
5368
5738
  children: [
@@ -6502,7 +6872,7 @@ function usePresentationClipboard({ content, mode, targetId, selectedSlide, sele
6502
6872
  const selectedElement = selectedElements.at(-1) ?? null;
6503
6873
  const copySelection = useCallback(()=>{
6504
6874
  if (selectedElements.length) {
6505
- copyPresentationElements(selectedElements);
6875
+ copyPresentationElements(selectedElements, 'slide' === mode ? selectedSlide?.animations : void 0);
6506
6876
  showToast(selectedElements.length > 1 ? `已复制 ${selectedElements.length} 个对象` : '已复制演示元素', 'success');
6507
6877
  return true;
6508
6878
  }
@@ -6518,7 +6888,7 @@ function usePresentationClipboard({ content, mode, targetId, selectedSlide, sele
6518
6888
  const deleteSelectedElement = useCallback(()=>{
6519
6889
  if (!selectedElements.length || !targetId) return false;
6520
6890
  const selectedIds = new Set(selectedElements.map((element)=>element.id));
6521
- const next = updateTargetElements(content, mode, targetId, (elements)=>elements.filter((element)=>!selectedIds.has(element.id)));
6891
+ const next = updateTargetElements(content, mode, targetId, (elements)=>elements.filter((element)=>!selectedIds.has(element.id)), selectedIds);
6522
6892
  if (!next) return false;
6523
6893
  onChange(next);
6524
6894
  onSelectElements([]);
@@ -6533,7 +6903,7 @@ function usePresentationClipboard({ content, mode, targetId, selectedSlide, sele
6533
6903
  ]);
6534
6904
  const cutSelection = useCallback(()=>{
6535
6905
  if (selectedElements.length) {
6536
- copyPresentationElements(selectedElements);
6906
+ copyPresentationElements(selectedElements, 'slide' === mode ? selectedSlide?.animations : void 0);
6537
6907
  if (!deleteSelectedElement()) return false;
6538
6908
  showToast(selectedElements.length > 1 ? `已剪切 ${selectedElements.length} 个对象` : '已剪切演示元素', 'success');
6539
6909
  return true;
@@ -6572,17 +6942,25 @@ function usePresentationClipboard({ content, mode, targetId, selectedSlide, sele
6572
6942
  }
6573
6943
  if ('element' === clipboard.payload.kind || 'elements' === clipboard.payload.kind) {
6574
6944
  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);
6945
+ const sourceElements = 'element' === clipboard.payload.kind ? [
6946
+ clipboard.payload.element
6947
+ ] : clipboard.payload.elements;
6948
+ const sourceAnimations = 'element' === clipboard.payload.kind ? clipboard.payload.animation ? [
6949
+ clipboard.payload.animation
6950
+ ] : [] : clipboard.payload.animations ?? [];
6951
+ const pasted = clonePresentationElementsAndAnimationsForPaste(sourceElements, sourceAnimations, clipboard.offset);
6952
+ if ('slide' === mode && !canAppendSlideAnimations(content, targetId, pasted.animations)) {
6953
+ showToast('对象动画数量已达到每张幻灯片 256 条的上限。', 'info');
6954
+ return true;
6955
+ }
6578
6956
  const next = updateTargetElements(content, mode, targetId, (elements)=>[
6579
6957
  ...elements,
6580
- ...pasted
6581
- ]);
6958
+ ...pasted.elements
6959
+ ], void 0, pasted.animations);
6582
6960
  if (!next) return false;
6583
6961
  onChange(next);
6584
- onSelectElements(pasted.map((element)=>element.id));
6585
- showToast(pasted.length > 1 ? `已粘贴 ${pasted.length} 个对象` : '已粘贴演示元素', 'success');
6962
+ onSelectElements(pasted.elements.map((element)=>element.id));
6963
+ showToast(pasted.elements.length > 1 ? `已粘贴 ${pasted.elements.length} 个对象` : '已粘贴演示元素', 'success');
6586
6964
  return true;
6587
6965
  }
6588
6966
  if ('slide' !== mode || !selectedSlide) {
@@ -6614,15 +6992,19 @@ function usePresentationClipboard({ content, mode, targetId, selectedSlide, sele
6614
6992
  ]);
6615
6993
  const duplicateSelection = useCallback(()=>{
6616
6994
  if (selectedElements.length && targetId) {
6617
- const copies = clonePresentationElementsForPaste(selectedElements, 5);
6995
+ const copies = clonePresentationElementsAndAnimationsForPaste(selectedElements, 'slide' === mode ? selectedSlide?.animations ?? [] : [], 5);
6996
+ if ('slide' === mode && !canAppendSlideAnimations(content, targetId, copies.animations)) {
6997
+ showToast('对象动画数量已达到每张幻灯片 256 条的上限。', 'info');
6998
+ return true;
6999
+ }
6618
7000
  const next = updateTargetElements(content, mode, targetId, (elements)=>[
6619
7001
  ...elements,
6620
- ...copies
6621
- ]);
7002
+ ...copies.elements
7003
+ ], void 0, copies.animations);
6622
7004
  if (!next) return false;
6623
7005
  onChange(next);
6624
- onSelectElements(copies.map((element)=>element.id));
6625
- showToast(copies.length > 1 ? `已复制 ${copies.length} 个对象` : '已复制演示元素', 'success');
7006
+ onSelectElements(copies.elements.map((element)=>element.id));
7007
+ showToast(copies.elements.length > 1 ? `已复制 ${copies.elements.length} 个对象` : '已复制演示元素', 'success');
6626
7008
  return true;
6627
7009
  }
6628
7010
  if ('slide' !== mode || !selectedSlide) return false;
@@ -6709,15 +7091,15 @@ function usePresentationClipboard({ content, mode, targetId, selectedSlide, sele
6709
7091
  toggleUnderline
6710
7092
  };
6711
7093
  }
6712
- function updateTargetElements(content, mode, targetId, update) {
7094
+ function updateTargetElements(content, mode, targetId, update, removedElementIds, addedAnimations) {
6713
7095
  if ('slide' === mode) {
6714
7096
  if (!content.slides.some((slide)=>slide.id === targetId)) return null;
6715
7097
  return {
6716
7098
  ...content,
6717
- slides: content.slides.map((slide)=>slide.id === targetId ? {
7099
+ slides: content.slides.map((slide)=>slide.id === targetId ? appendWorkSlideAnimations(removeWorkSlideAnimationsForElements({
6718
7100
  ...slide,
6719
7101
  elements: update(use_presentation_clipboard_structuredCopy(slide.elements))
6720
- } : slide)
7102
+ }, removedElementIds ?? new Set()), addedAnimations) : slide)
6721
7103
  };
6722
7104
  }
6723
7105
  const normalized = withPresentationDesign(content);
@@ -6740,10 +7122,130 @@ function updateTargetElements(content, mode, targetId, update) {
6740
7122
  } : master)
6741
7123
  };
6742
7124
  }
7125
+ function canAppendSlideAnimations(content, slideId, animations) {
7126
+ if (!animations?.length) return true;
7127
+ const slide = content.slides.find((candidate)=>candidate.id === slideId);
7128
+ return Boolean(slide) && (slide?.animations?.length ?? 0) + animations.length <= 256;
7129
+ }
7130
+ function appendWorkSlideAnimations(slide, animations) {
7131
+ if (!animations?.length) return slide;
7132
+ return {
7133
+ ...slide,
7134
+ animations: [
7135
+ ...slide.animations ?? [],
7136
+ ...use_presentation_clipboard_structuredCopy(animations)
7137
+ ]
7138
+ };
7139
+ }
6743
7140
  function use_presentation_clipboard_structuredCopy(value) {
6744
7141
  if ('function' == typeof structuredClone) return structuredClone(value);
6745
7142
  return JSON.parse(JSON.stringify(value));
6746
7143
  }
7144
+ function usePresentationAnimationCommands({ content, onChange, selectedElementId, selectedSlide }) {
7145
+ const selectedElementExists = Boolean(selectedElementId && selectedSlide.elements.some((element)=>element.id === selectedElementId));
7146
+ const selectedAnimation = workSlideAnimationForElement(selectedSlide, selectedElementId);
7147
+ const setEntranceAnimation = useCallback((effect)=>{
7148
+ if (!selectedElementId || !selectedElementExists) return false;
7149
+ const current = workSlideAnimationForElement(selectedSlide, selectedElementId);
7150
+ if (!effect) {
7151
+ if (!current) return false;
7152
+ updateSlide(content, selectedSlide.id, (slide)=>{
7153
+ const animations = slide.animations?.filter((animation)=>animation.id !== current.id);
7154
+ return {
7155
+ ...slide,
7156
+ animations: animations?.length ? animations : void 0
7157
+ };
7158
+ }, onChange);
7159
+ return true;
7160
+ }
7161
+ if (current?.effect === effect) return false;
7162
+ if (!current && (selectedSlide.animations?.length ?? 0) >= 256) return false;
7163
+ updateSlide(content, selectedSlide.id, (slide)=>{
7164
+ const next = createWorkSlideAnimation(selectedElementId, effect, current);
7165
+ const animations = [
7166
+ ...slide.animations ?? []
7167
+ ];
7168
+ const index = current ? animations.findIndex((animation)=>animation.id === current.id) : -1;
7169
+ if (index >= 0) animations[index] = next;
7170
+ else animations.push(next);
7171
+ return {
7172
+ ...slide,
7173
+ animations
7174
+ };
7175
+ }, onChange);
7176
+ return true;
7177
+ }, [
7178
+ content,
7179
+ onChange,
7180
+ selectedElementExists,
7181
+ selectedElementId,
7182
+ selectedSlide
7183
+ ]);
7184
+ const updateEntranceAnimation = useCallback((patch)=>{
7185
+ if (!selectedAnimation) return false;
7186
+ const next = normalizeWorkSlideAnimation({
7187
+ ...selectedAnimation,
7188
+ ...patch,
7189
+ id: selectedAnimation.id,
7190
+ elementId: selectedAnimation.elementId
7191
+ });
7192
+ if (animationsEqual(selectedAnimation, next)) return false;
7193
+ updateSlide(content, selectedSlide.id, (slide)=>({
7194
+ ...slide,
7195
+ animations: slide.animations?.map((animation)=>animation.id === selectedAnimation.id ? next : animation)
7196
+ }), onChange);
7197
+ return true;
7198
+ }, [
7199
+ content,
7200
+ onChange,
7201
+ selectedAnimation,
7202
+ selectedSlide.id
7203
+ ]);
7204
+ const canMoveEntranceAnimation = useCallback((direction)=>{
7205
+ const index = workSlideAnimationIndex(selectedSlide, selectedAnimation?.id);
7206
+ const target = index + direction;
7207
+ return index >= 0 && target >= 0 && target < (selectedSlide.animations?.length ?? 0);
7208
+ }, [
7209
+ selectedAnimation?.id,
7210
+ selectedSlide
7211
+ ]);
7212
+ const moveEntranceAnimation = useCallback((direction)=>{
7213
+ if (!selectedAnimation || !canMoveEntranceAnimation(direction)) return false;
7214
+ updateSlide(content, selectedSlide.id, (slide)=>{
7215
+ const animations = [
7216
+ ...slide.animations ?? []
7217
+ ];
7218
+ const index = animations.findIndex((animation)=>animation.id === selectedAnimation.id);
7219
+ const target = index + direction;
7220
+ [animations[index], animations[target]] = [
7221
+ animations[target],
7222
+ animations[index]
7223
+ ];
7224
+ return {
7225
+ ...slide,
7226
+ animations
7227
+ };
7228
+ }, onChange);
7229
+ return true;
7230
+ }, [
7231
+ canMoveEntranceAnimation,
7232
+ content,
7233
+ onChange,
7234
+ selectedAnimation,
7235
+ selectedSlide.id
7236
+ ]);
7237
+ return {
7238
+ canMoveEntranceAnimation,
7239
+ canSetEntranceAnimation: selectedElementExists,
7240
+ canUpdateEntranceAnimation: Boolean(selectedAnimation),
7241
+ moveEntranceAnimation,
7242
+ setEntranceAnimation,
7243
+ updateEntranceAnimation
7244
+ };
7245
+ }
7246
+ function animationsEqual(left, right) {
7247
+ 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;
7248
+ }
6747
7249
  function usePresentationDesignCommands({ activeElements, activeTargetId, designContent, designMode, designOpen, onChange, onClearSelection, onCloseComments, onEditElement, onSetDesignMode, onSetDesignOpen, selectedLayout, selectedMaster, selectedSlide }) {
6748
7250
  const updateLayout = useCallback((layoutId, update)=>{
6749
7251
  onChange({
@@ -7390,9 +7892,12 @@ function usePresentationReviewCommands({ content, onChange, onClearSelection, on
7390
7892
  function activePresentationReviewInvoker() {
7391
7893
  return "u" > typeof document && document.activeElement instanceof HTMLElement ? document.activeElement : null;
7392
7894
  }
7393
- function usePresentationSelection(elements) {
7394
- const [selectedElementIds, setSelectedElementIds] = useState([]);
7395
- const [editingElementId, setEditingElementId] = useState(null);
7895
+ function usePresentationSelection(elements, initialEditingElementId = null) {
7896
+ const initialEditingElementExists = Boolean(initialEditingElementId && elements.some((element)=>element.id === initialEditingElementId));
7897
+ const [selectedElementIds, setSelectedElementIds] = useState(()=>initialEditingElementExists && initialEditingElementId ? selectionWithPendingIds(elements, [
7898
+ initialEditingElementId
7899
+ ]) : []);
7900
+ const [editingElementId, setEditingElementId] = useState(()=>initialEditingElementExists ? initialEditingElementId : null);
7396
7901
  const clear = useCallback(()=>{
7397
7902
  setSelectedElementIds([]);
7398
7903
  setEditingElementId(null);
@@ -7991,7 +8496,7 @@ function PresentationEditingSurface({ initialSlide, autoFocus = true, collaborat
7991
8496
  const selectedLayout = designContent.layouts?.find((layout)=>layout.id === selectedSlide?.layoutId) ?? designContent.layouts?.[0];
7992
8497
  const selectedMaster = designContent.masters?.find((master)=>master.id === selectedLayout?.masterId) ?? designContent.masters?.[0];
7993
8498
  const activeElements = 'layout' === designMode ? selectedLayout?.elements ?? [] : 'master' === designMode ? selectedMaster?.elements ?? [] : selectedSlide?.elements ?? [];
7994
- const selection = usePresentationSelection(activeElements);
8499
+ const selection = usePresentationSelection(activeElements, autoFocus ? presentationInitialEditingElementId(activeElements) : null);
7995
8500
  const presentationPresenceLocation = useMemo(()=>({
7996
8501
  kind: 'presentation',
7997
8502
  slideId: selectedSlide.id,
@@ -8142,6 +8647,12 @@ function PresentationEditingSurface({ initialSlide, autoFocus = true, collaborat
8142
8647
  onSelectSlide: setSelectedSlideId,
8143
8648
  selectedSlide
8144
8649
  });
8650
+ const presentationAnimations = usePresentationAnimationCommands({
8651
+ content,
8652
+ onChange,
8653
+ selectedElementId: 'slide' === designMode && singleSelectedElement ? singleSelectedElement.id : void 0,
8654
+ selectedSlide
8655
+ });
8145
8656
  const presentationElements = usePresentationElementCommands({
8146
8657
  activeElements,
8147
8658
  activeTargetId,
@@ -8274,8 +8785,30 @@ function PresentationEditingSurface({ initialSlide, autoFocus = true, collaborat
8274
8785
  selectedTextEditor,
8275
8786
  toolbarSelectedElement
8276
8787
  ]);
8788
+ const startPresentationSlideshow = useCallback((source)=>{
8789
+ const currentIndex = content.slides.findIndex((slide)=>slide.id === selectedSlide.id);
8790
+ const activeElement = document.activeElement;
8791
+ slideshowReturnFocusRef.current = activeElement instanceof HTMLElement && activeElement !== document.body ? activeElement : document.querySelector(`[data-presentation-slideshow-source="${source}"]`);
8792
+ setAgentMenu(null);
8793
+ setSlideshowStartIndex('current' === source ? Math.max(0, currentIndex) : 0);
8794
+ onStartSlideshow?.();
8795
+ }, [
8796
+ content.slides,
8797
+ onStartSlideshow,
8798
+ selectedSlide.id
8799
+ ]);
8277
8800
  const presentationExtensions = useMemo(createPresentationEditorExtensions, []);
8278
8801
  const presentationEditor = useOfficeEditorRuntime({
8802
+ animations: {
8803
+ canMoveEntranceAnimation: (direction)=>'slide' === designMode && presentationAnimations.canMoveEntranceAnimation(direction),
8804
+ canPreviewAnimations: 'slide' === designMode && Boolean(selectedSlide.animations?.length),
8805
+ canSetEntranceAnimation: 'slide' === designMode && presentationAnimations.canSetEntranceAnimation,
8806
+ canUpdateEntranceAnimation: 'slide' === designMode && presentationAnimations.canUpdateEntranceAnimation,
8807
+ moveEntranceAnimation: presentationAnimations.moveEntranceAnimation,
8808
+ previewAnimations: ()=>startPresentationSlideshow('current'),
8809
+ setEntranceAnimation: presentationAnimations.setEntranceAnimation,
8810
+ updateEntranceAnimation: presentationAnimations.updateEntranceAnimation
8811
+ },
8279
8812
  clipboard: {
8280
8813
  canCopySelection: selectedElements.length > 0 || 'slide' === designMode && Boolean(selectedSlide),
8281
8814
  canCutSelection: selectedElements.length > 0 || 'slide' === designMode && Boolean(selectedSlide),
@@ -8392,14 +8925,7 @@ function PresentationEditingSurface({ initialSlide, autoFocus = true, collaborat
8392
8925
  view: {
8393
8926
  canStartSlideshow: 'slide' === designMode && content.slides.length > 0,
8394
8927
  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
- },
8928
+ startSlideshow: startPresentationSlideshow,
8403
8929
  toggleDesign: presentationDesign.toggleDesignPanel
8404
8930
  }
8405
8931
  }, presentationExtensions);