@a3s-lab/office 0.38.1 → 0.39.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.
@@ -566,7 +566,7 @@ async function parseSlide(archive, slidePart, slideNumber, slideWidthEmu, slideH
566
566
  addIssue(context, 'pptx.transition', 'Slide transitions', transition.transition ? 'Basic slide transitions and advance timing are preserved, editable, and replayed in presentation preview.' : 'This slide transition is not editable and remains in the original PPTX only.', transition.transition ? 'info' : 'warning');
567
567
  for (const diagnostic of transition.diagnostics)addIssue(context, diagnostic.code, 'Slide transitions', diagnostic.message);
568
568
  }
569
- if (animationResult.animations.length) addIssue(context, 'pptx.animation', 'Animations', `${animationResult.animations.length} supported object entrance animation(s) are preserved, editable, replayed, and exported.`, 'info');
569
+ if (animationResult.animations.length) addIssue(context, 'pptx.animation', 'Animations', `${animationResult.animations.length} supported object entrance or exit animation(s) are preserved, editable, replayed, and exported.`, 'info');
570
570
  for (const diagnostic of animationResult.diagnostics)addIssue(context, diagnostic.code, 'Animations', diagnostic.message);
571
571
  for (const relationship of relationships.values())if (relationship.type.includes('/audio') || relationship.type.includes('/video') || relationship.type.includes('/media')) addIssue(context, 'pptx.media', 'Embedded media', 'Audio and video remain in the original file but are not playable yet.');
572
572
  else if (relationship.type.endsWith('/oleObject') || relationship.type.endsWith('/package')) addIssue(context, 'pptx.ole', 'Embedded objects', 'Embedded Office or OLE objects remain in the original file only.');
@@ -0,0 +1,38 @@
1
+ const CARDINAL_DIRECTIONS = new Set([
2
+ 'left',
3
+ 'right',
4
+ 'up',
5
+ 'down'
6
+ ]);
7
+ const SPLIT_DIRECTIONS = new Set([
8
+ 'in',
9
+ 'out'
10
+ ]);
11
+ function createWorkSlideTransition(type, previous) {
12
+ const common = {
13
+ type,
14
+ speed: previous?.speed ?? 'medium',
15
+ advanceOnClick: previous?.advanceOnClick ?? true,
16
+ advanceAfterMs: previous?.advanceAfterMs
17
+ };
18
+ if ('push' === type || 'wipe' === type) return {
19
+ ...common,
20
+ direction: previous?.direction && CARDINAL_DIRECTIONS.has(previous.direction) ? previous.direction : 'left'
21
+ };
22
+ if ('split' === type) return {
23
+ ...common,
24
+ direction: previous?.direction && SPLIT_DIRECTIONS.has(previous.direction) ? previous.direction : 'out',
25
+ orientation: previous?.orientation ?? 'horizontal'
26
+ };
27
+ return common;
28
+ }
29
+ function slideTransitionDurationMilliseconds(transition) {
30
+ if (transition?.speed === 'fast') return 500;
31
+ if (transition?.speed === 'slow') return 2000;
32
+ return 1000;
33
+ }
34
+ function workSlideTransitionsEqual(left, right) {
35
+ if (!left || !right) return left === right;
36
+ return left.type === right.type && left.speed === right.speed && left.direction === right.direction && left.orientation === right.orientation && left.advanceOnClick === right.advanceOnClick && left.advanceAfterMs === right.advanceAfterMs;
37
+ }
38
+ export { createWorkSlideTransition, slideTransitionDurationMilliseconds, workSlideTransitionsEqual };
package/dist/4121.js CHANGED
@@ -2929,8 +2929,8 @@ const WORK_TEMPLATES = [
2929
2929
  {
2930
2930
  id: 'animated-deck',
2931
2931
  kind: 'presentation',
2932
- name: '入场动画',
2933
- description: '出现、淡入、飞入、缩放与三种触发方式',
2932
+ name: '进入与退出动画',
2933
+ description: '进入、退出、触发、计时、排序与放映预览',
2934
2934
  accent: '#7657c8'
2935
2935
  }
2936
2936
  ];
@@ -2967,7 +2967,7 @@ function initialTitle(templateId, kind) {
2967
2967
  'data-validation': '数据验证示例',
2968
2968
  'structured-references': '结构化引用示例',
2969
2969
  'strategy-deck': '业务策略汇报',
2970
- 'animated-deck': '入场动画示例'
2970
+ 'animated-deck': '进入与退出动画示例'
2971
2971
  };
2972
2972
  if (titles[templateId]) return titles[templateId];
2973
2973
  if ('document' === kind) return '无标题文字';
@@ -4115,9 +4115,9 @@ function animatedPresentation() {
4115
4115
  slides: [
4116
4116
  {
4117
4117
  id: createWorkId('slide'),
4118
- name: '入场动画',
4118
+ name: '进入与退出',
4119
4119
  background: '#17162b',
4120
- notes: '从“动画”选项卡编辑效果、触发方式、持续时间、延迟和顺序,再从头放映验证逐步播放。',
4120
+ notes: '从“动画”选项卡分别编辑进入与退出效果、触发方式、持续时间、延迟和顺序,再从头放映验证逐步播放。',
4121
4121
  elements: [
4122
4122
  {
4123
4123
  id: titleId,
@@ -4126,7 +4126,7 @@ function animatedPresentation() {
4126
4126
  y: 10,
4127
4127
  width: 76,
4128
4128
  height: 16,
4129
- text: '让信息按叙事顺序出现',
4129
+ text: '让对象按叙事顺序登场与退场',
4130
4130
  fontSize: 34,
4131
4131
  color: '#ffffff',
4132
4132
  fill: 'transparent',
@@ -4140,7 +4140,7 @@ function animatedPresentation() {
4140
4140
  y: 28,
4141
4141
  width: 68,
4142
4142
  height: 9,
4143
- text: '单击开始,下一对象与标题同时淡入',
4143
+ text: '一次单击完成入场,再次单击依次退场',
4144
4144
  fontSize: 16,
4145
4145
  color: '#b9b7dc',
4146
4146
  fill: 'transparent',
@@ -4154,7 +4154,7 @@ function animatedPresentation() {
4154
4154
  y: 46,
4155
4155
  width: 52,
4156
4156
  height: 20,
4157
- text: '上一动画完成后,从左侧飞入',
4157
+ text: '从左侧飞入,再向右侧飞出',
4158
4158
  fontSize: 19,
4159
4159
  color: '#211f35',
4160
4160
  fill: '#d9d3ff',
@@ -4169,7 +4169,7 @@ function animatedPresentation() {
4169
4169
  y: 46,
4170
4170
  width: 26,
4171
4171
  height: 20,
4172
- text: '再次单击\n缩放结论',
4172
+ text: '最后缩放\n进入与退出',
4173
4173
  fontSize: 17,
4174
4174
  color: '#ffffff',
4175
4175
  fill: '#7657c8',
@@ -4208,7 +4208,40 @@ function animatedPresentation() {
4208
4208
  id: createWorkId('slide-animation'),
4209
4209
  elementId: conclusionId,
4210
4210
  effect: 'zoom',
4211
+ trigger: 'after-previous',
4212
+ durationMs: 650,
4213
+ delayMs: 0
4214
+ },
4215
+ {
4216
+ id: createWorkId('slide-animation'),
4217
+ elementId: titleId,
4218
+ effect: 'disappear',
4211
4219
  trigger: 'on-click',
4220
+ durationMs: 300,
4221
+ delayMs: 0
4222
+ },
4223
+ {
4224
+ id: createWorkId('slide-animation'),
4225
+ elementId: subtitleId,
4226
+ effect: 'fade-out',
4227
+ trigger: 'with-previous',
4228
+ durationMs: 600,
4229
+ delayMs: 100
4230
+ },
4231
+ {
4232
+ id: createWorkId('slide-animation'),
4233
+ elementId: sequenceId,
4234
+ effect: 'fly-out',
4235
+ trigger: 'after-previous',
4236
+ durationMs: 700,
4237
+ delayMs: 150,
4238
+ direction: 'right'
4239
+ },
4240
+ {
4241
+ id: createWorkId('slide-animation'),
4242
+ elementId: conclusionId,
4243
+ effect: 'zoom-out',
4244
+ trigger: 'after-previous',
4212
4245
  durationMs: 650,
4213
4246
  delayMs: 0
4214
4247
  }
package/dist/4560.js CHANGED
@@ -1,6 +1,117 @@
1
+ import { createOfficeId as createWorkId } from "./4121.js";
1
2
  import { initializeWorkOfficeCollaborationMetadata, assertWorkOfficeCollaborationEditable, OfficeCollaborationError as WorkOfficeCollaborationError, readOfficeCollaborationMetadata as readWorkOfficeCollaborationMetadata, registerWorkOfficeCollaborationInitializer, markWorkOfficeCollaborationInitialized, assertWorkOfficeCollaborationOrigin } from "./9787.js";
2
3
  import { workOfficeCollaborationJsonEqual, canonicalWorkOfficeCollaborationJson, isWorkOfficeCollaborationRecord, cloneWorkOfficeCollaborationJson } from "./4650.js";
3
4
  import * as __rspack_external_yjs from "yjs";
5
+ function createWorkSlideAnimation(elementId, effect, previous) {
6
+ return normalizeWorkSlideAnimation({
7
+ id: previous?.id ?? createWorkId('slide-animation'),
8
+ elementId,
9
+ effect,
10
+ trigger: previous?.trigger ?? 'on-click',
11
+ durationMs: previous?.durationMs ?? 500,
12
+ delayMs: previous?.delayMs ?? 0,
13
+ direction: isFlyAnimationEffect(effect) ? previous?.direction ?? 'left' : void 0
14
+ });
15
+ }
16
+ function normalizeWorkSlideAnimation(animation) {
17
+ return {
18
+ ...animation,
19
+ durationMs: boundedInteger(animation.durationMs, 100, 60000),
20
+ delayMs: boundedInteger(animation.delayMs, 0, 60000),
21
+ direction: isFlyAnimationEffect(animation.effect) ? normalizeWorkSlideAnimationDirection(animation.direction) : void 0
22
+ };
23
+ }
24
+ function workSlideAnimationClass(effect) {
25
+ return 'disappear' === effect || 'fade-out' === effect || 'fly-out' === effect || 'zoom-out' === effect ? 'exit' : 'entrance';
26
+ }
27
+ function workSlideAnimationEffectMatchesClass(effect, animationClass) {
28
+ return workSlideAnimationClass(effect) === animationClass;
29
+ }
30
+ function workSlideAnimationForElement(slide, elementId, animationClass) {
31
+ if (!elementId) return;
32
+ return slide.animations?.find((animation)=>animation.elementId === elementId && workSlideAnimationClass(animation.effect) === animationClass);
33
+ }
34
+ function workSlideAnimationIndex(slide, animationId) {
35
+ if (!animationId) return -1;
36
+ return slide.animations?.findIndex((animation)=>animation.id === animationId) ?? -1;
37
+ }
38
+ function workSlideAnimationCues(animations) {
39
+ const cues = [];
40
+ let current;
41
+ for (const raw of animations ?? []){
42
+ const animation = normalizeWorkSlideAnimation(raw);
43
+ if (!current || 'on-click' === animation.trigger) {
44
+ current = {
45
+ automatic: 'on-click' !== animation.trigger,
46
+ items: [],
47
+ totalDurationMs: 0
48
+ };
49
+ cues.push(current);
50
+ }
51
+ const previous = current.items.at(-1);
52
+ const baseStart = 'after-previous' === animation.trigger ? previous?.endOffsetMs ?? 0 : 'with-previous' === animation.trigger ? previous?.startOffsetMs ?? 0 : 0;
53
+ const startOffsetMs = baseStart + animation.delayMs;
54
+ const endOffsetMs = startOffsetMs + animation.durationMs;
55
+ current.items.push({
56
+ animation,
57
+ startOffsetMs,
58
+ endOffsetMs
59
+ });
60
+ current.totalDurationMs = Math.max(current.totalDurationMs, endOffsetMs);
61
+ }
62
+ return cues;
63
+ }
64
+ function initialWorkSlideAnimationCueIndex(cues) {
65
+ return cues[0]?.automatic ? 0 : -1;
66
+ }
67
+ function workSlideAnimationSequenceIssue(animations) {
68
+ const classTargets = new Set();
69
+ for (const animation of animations ?? []){
70
+ const key = `${animation.elementId}\u0000${workSlideAnimationClass(animation.effect)}`;
71
+ if (classTargets.has(key)) return 'duplicate-class';
72
+ classTargets.add(key);
73
+ }
74
+ for (const cue of workSlideAnimationCues(animations)){
75
+ const rangesByTarget = new Map();
76
+ for (const item of cue.items){
77
+ const ranges = rangesByTarget.get(item.animation.elementId) ?? [];
78
+ if (ranges.some((range)=>item.startOffsetMs < range.endOffsetMs && item.endOffsetMs > range.startOffsetMs)) return 'overlapping-target';
79
+ ranges.push(item);
80
+ rangesByTarget.set(item.animation.elementId, ranges);
81
+ }
82
+ }
83
+ }
84
+ function removeWorkSlideAnimationsForElements(slide, elementIds) {
85
+ const animations = slide.animations?.filter((animation)=>!elementIds.has(animation.elementId));
86
+ if (animations?.length === slide.animations?.length) return slide;
87
+ return {
88
+ ...slide,
89
+ animations: animations?.length ? animations : void 0
90
+ };
91
+ }
92
+ function remapWorkSlideAnimations(animations, elementIds) {
93
+ const remapped = (animations ?? []).flatMap((animation)=>{
94
+ const elementId = elementIds.get(animation.elementId);
95
+ return elementId ? [
96
+ {
97
+ ...animation,
98
+ id: createWorkId('slide-animation'),
99
+ elementId
100
+ }
101
+ ] : [];
102
+ });
103
+ return remapped.length ? remapped : void 0;
104
+ }
105
+ function normalizeWorkSlideAnimationDirection(direction) {
106
+ return 'right' === direction || 'up' === direction || 'down' === direction ? direction : 'left';
107
+ }
108
+ function isFlyAnimationEffect(effect) {
109
+ return 'fly-in' === effect || 'fly-out' === effect;
110
+ }
111
+ function boundedInteger(value, minimum, maximum) {
112
+ const finite = Number.isFinite(value) ? Math.round(value) : minimum;
113
+ return Math.min(maximum, Math.max(minimum, finite));
114
+ }
4
115
  function validateWorkOfficePresentationContent(content) {
5
116
  if (!content || 'presentation' !== content.type) invalidWorkOfficePresentationInput('a Presentation content value');
6
117
  const result = {
@@ -50,30 +161,34 @@ function validateSlide(value) {
50
161
  function validateSlideAnimations(value, slide) {
51
162
  if (!Array.isArray(value) || value.length > 256) invalidWorkOfficePresentationInput("an array of at most 256 slide animations");
52
163
  const animationIds = new Set();
53
- const targetIds = new Set();
164
+ const classTargetIds = new Set();
54
165
  const elementIds = new Set(slide.elements.map((element)=>element.id));
55
- return value.map((item)=>{
166
+ const animations = value.map((item)=>{
56
167
  const record = requiredInputRecord(item, 'slide animation');
57
168
  const animation = validateJsonRecord(record, 'slide animation');
58
169
  animation.id = requiredIdentifier(record.id, 'slide animation');
59
170
  animation.elementId = requiredIdentifier(record.elementId, 'slide animation element');
60
171
  if (animationIds.has(animation.id)) invalidWorkOfficePresentationInput(`a unique slide animation ID; '${animation.id}' is repeated`);
61
172
  if (!elementIds.has(animation.elementId)) invalidWorkOfficePresentationInput(`slide animation '${animation.id}' to reference an existing element`);
62
- if (targetIds.has(animation.elementId)) invalidWorkOfficePresentationInput(`at most one entrance animation for element '${animation.elementId}'`);
63
- if ('appear' !== record.effect && 'fade' !== record.effect && 'fly-in' !== record.effect && 'zoom' !== record.effect) invalidWorkOfficePresentationInput('a supported slide entrance animation effect');
173
+ if ('appear' !== record.effect && 'fade' !== record.effect && 'fly-in' !== record.effect && 'zoom' !== record.effect && 'disappear' !== record.effect && 'fade-out' !== record.effect && 'fly-out' !== record.effect && 'zoom-out' !== record.effect) invalidWorkOfficePresentationInput('a supported slide entrance or exit animation effect');
64
174
  animation.effect = record.effect;
175
+ const animationClass = workSlideAnimationClass(animation.effect);
176
+ const classTargetId = `${animation.elementId}\u0000${animationClass}`;
177
+ if (classTargetIds.has(classTargetId)) invalidWorkOfficePresentationInput(`at most one ${animationClass} animation for element '${animation.elementId}'`);
65
178
  if ('on-click' !== record.trigger && 'with-previous' !== record.trigger && 'after-previous' !== record.trigger) invalidWorkOfficePresentationInput('a supported slide animation trigger');
66
179
  animation.trigger = record.trigger;
67
180
  animation.durationMs = requiredIntegerInRange(record.durationMs, 100, 60000, 'slide animation duration');
68
181
  animation.delayMs = requiredIntegerInRange(record.delayMs, 0, 60000, 'slide animation delay');
69
- if ('fly-in' === animation.effect) {
70
- if ('left' !== record.direction && 'right' !== record.direction && 'up' !== record.direction && 'down' !== record.direction) invalidWorkOfficePresentationInput('a direction for each fly-in animation');
182
+ if ('fly-in' === animation.effect || 'fly-out' === animation.effect) {
183
+ if ('left' !== record.direction && 'right' !== record.direction && 'up' !== record.direction && 'down' !== record.direction) invalidWorkOfficePresentationInput('a direction for each fly-in or fly-out animation');
71
184
  animation.direction = record.direction;
72
- } else if (void 0 !== record.direction) invalidWorkOfficePresentationInput('slide animation directions only on fly-in effects');
185
+ } else if (void 0 !== record.direction) invalidWorkOfficePresentationInput('slide animation directions only on fly-in or fly-out effects');
73
186
  animationIds.add(animation.id);
74
- targetIds.add(animation.elementId);
187
+ classTargetIds.add(classTargetId);
75
188
  return animation;
76
189
  });
190
+ if ('overlapping-target' === workSlideAnimationSequenceIssue(animations)) invalidWorkOfficePresentationInput('animations for one element to avoid overlapping inside one playback cue');
191
+ return animations;
77
192
  }
78
193
  function validateMaster(value) {
79
194
  const record = requiredInputRecord(value, 'presentation master');
@@ -857,4 +972,4 @@ function assertInitializedPresentationSession(session) {
857
972
  if (metadata?.initialized) return;
858
973
  throw new WorkOfficeCollaborationError('office.collaboration.not_initialized', 'The Presentation collaboration session has not been initialized.');
859
974
  }
860
- export { createWorkOfficePresentationCollaborationBinding as createOfficePresentationCollaborationBinding, initializeWorkOfficePresentationCollaboration as initializeOfficePresentationCollaboration, readWorkOfficePresentationCollaboration as readOfficePresentationCollaboration, replaceWorkOfficePresentationCollaboration as replaceOfficePresentationCollaboration };
975
+ export { createWorkOfficePresentationCollaborationBinding as createOfficePresentationCollaborationBinding, createWorkSlideAnimation, initialWorkSlideAnimationCueIndex, initializeWorkOfficePresentationCollaboration as initializeOfficePresentationCollaboration, normalizeWorkSlideAnimation, readWorkOfficePresentationCollaboration as readOfficePresentationCollaboration, remapWorkSlideAnimations, removeWorkSlideAnimationsForElements, replaceWorkOfficePresentationCollaboration as replaceOfficePresentationCollaboration, workSlideAnimationClass, workSlideAnimationCues, workSlideAnimationEffectMatchesClass, workSlideAnimationForElement, workSlideAnimationIndex, workSlideAnimationSequenceIssue };
@@ -1,11 +1,13 @@
1
- import type { WorkSlideAnimation, WorkSlideAnimationEffect } from '../work-types';
2
- export declare function PresentationAnimationPanel({ animation, canMove, canPreview, editable, onMove, onPreview, onSetEffect, onUpdate, }: {
3
- animation: WorkSlideAnimation | undefined;
4
- canMove: (direction: -1 | 1) => boolean;
1
+ import type { WorkSlideAnimation, WorkSlideAnimationClass, WorkSlideAnimationEffect } from '../work-types';
2
+ export declare function PresentationAnimationPanel({ animations, canMove, canPreview, canUpdate, editable, elementId, onMove, onPreview, onSetEffect, onUpdate, }: {
3
+ animations: Record<WorkSlideAnimationClass, WorkSlideAnimation | undefined>;
4
+ canMove: (animationClass: WorkSlideAnimationClass, direction: -1 | 1) => boolean;
5
5
  canPreview: boolean;
6
+ canUpdate: (animationClass: WorkSlideAnimationClass, patch: Partial<WorkSlideAnimation>) => boolean;
6
7
  editable: boolean;
7
- onMove: (direction: -1 | 1) => void;
8
+ elementId: string | undefined;
9
+ onMove: (animationClass: WorkSlideAnimationClass, direction: -1 | 1) => void;
8
10
  onPreview: () => void;
9
- onSetEffect: (effect: WorkSlideAnimationEffect | undefined) => void;
10
- onUpdate: (patch: Partial<WorkSlideAnimation>) => void;
11
+ onSetEffect: (animationClass: WorkSlideAnimationClass, effect: WorkSlideAnimationEffect | undefined) => void;
12
+ onUpdate: (animationClass: WorkSlideAnimationClass, patch: Partial<WorkSlideAnimation>) => void;
11
13
  }): import("react").JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import type { OfficeKernelPresentationAlignment } from '../../../kernel/office-kernel-protocol';
2
- import type { WorkPresentationContent, WorkSlideAnimation, WorkSlideAnimationEffect, WorkSlideElement, WorkSlideTransition, WorkSlideTextRun } from '../work-types';
2
+ import type { WorkPresentationContent, WorkSlideAnimation, WorkSlideAnimationClass, WorkSlideAnimationEffect, WorkSlideElement, WorkSlideTransition, WorkSlideTextRun } from '../work-types';
3
3
  import type { OfficeEditorCanCommands } from './office-editor-extension';
4
4
  import type { PresentationDesignMode } from './presentation-editor-types';
5
5
  import type { PresentationDistribution } from './presentation-selection';
@@ -37,7 +37,7 @@ export interface PresentationEditorCommands {
37
37
  groupElements: () => boolean;
38
38
  instantiatePlaceholder: (definition: WorkSlideElement) => PresentationCommandResult;
39
39
  locatePresentationComment: (slideId: string, commentId: string) => PresentationCommandResult;
40
- moveEntranceAnimation: (direction: -1 | 1) => PresentationCommandResult;
40
+ moveAnimation: (animationClass: WorkSlideAnimationClass, direction: -1 | 1) => PresentationCommandResult;
41
41
  nudgeSelection: (key: string, distance: number) => boolean;
42
42
  openComment: (commentId: string) => PresentationCommandResult;
43
43
  pasteSelection: () => boolean;
@@ -52,7 +52,7 @@ export interface PresentationEditorCommands {
52
52
  selectSlide: (slideId: string, returnToSlideMode?: boolean) => PresentationCommandResult;
53
53
  setPresentationContent: (content: WorkPresentationContent) => PresentationCommandResult;
54
54
  setBackground: (color: string) => PresentationCommandResult;
55
- setEntranceAnimation: (effect: WorkSlideAnimationEffect | undefined) => PresentationCommandResult;
55
+ setAnimation: (animationClass: WorkSlideAnimationClass, effect: WorkSlideAnimationEffect | undefined) => PresentationCommandResult;
56
56
  setPresentationLayoutBackground: (color: string | undefined) => PresentationCommandResult;
57
57
  setPresentationMasterBackground: (color: string) => PresentationCommandResult;
58
58
  setTransition: (transition: WorkSlideTransition | undefined) => PresentationCommandResult;
@@ -69,7 +69,7 @@ export interface PresentationEditorCommands {
69
69
  updateElement: (patch: Partial<WorkSlideElement>, options?: {
70
70
  restoreTextFocus?: boolean;
71
71
  }) => PresentationCommandResult;
72
- updateEntranceAnimation: (patch: Partial<WorkSlideAnimation>) => PresentationCommandResult;
72
+ updateAnimation: (animationClass: WorkSlideAnimationClass, patch: Partial<WorkSlideAnimation>) => PresentationCommandResult;
73
73
  updateNotes: (notes: string) => PresentationCommandResult;
74
74
  updatePresentationComment: (slideId: string, commentId: string, text: string) => PresentationCommandResult;
75
75
  updateTextElement: (elementId: string, value: {
@@ -112,14 +112,14 @@ export interface PresentationSlideCommandPort {
112
112
  updateNotes: (notes: string) => PresentationCommandResult;
113
113
  }
114
114
  export interface PresentationAnimationCommandPort {
115
- canMoveEntranceAnimation: (direction: -1 | 1) => boolean;
115
+ canMoveAnimation: (animationClass: WorkSlideAnimationClass, direction: -1 | 1) => boolean;
116
116
  canPreviewAnimations: boolean;
117
- canSetEntranceAnimation: boolean;
118
- canUpdateEntranceAnimation: boolean;
119
- moveEntranceAnimation: (direction: -1 | 1) => PresentationCommandResult;
117
+ canSetAnimation: boolean;
118
+ canUpdateAnimation: (animationClass: WorkSlideAnimationClass, patch: Partial<WorkSlideAnimation>) => boolean;
119
+ moveAnimation: (animationClass: WorkSlideAnimationClass, direction: -1 | 1) => PresentationCommandResult;
120
120
  previewAnimations: () => PresentationCommandResult;
121
- setEntranceAnimation: (effect: WorkSlideAnimationEffect | undefined) => PresentationCommandResult;
122
- updateEntranceAnimation: (patch: Partial<WorkSlideAnimation>) => PresentationCommandResult;
121
+ setAnimation: (animationClass: WorkSlideAnimationClass, effect: WorkSlideAnimationEffect | undefined) => PresentationCommandResult;
122
+ updateAnimation: (animationClass: WorkSlideAnimationClass, patch: Partial<WorkSlideAnimation>) => PresentationCommandResult;
123
123
  }
124
124
  export interface PresentationInsertCommandPort {
125
125
  enabled: boolean;
@@ -1,5 +1,6 @@
1
1
  import { type WorkPresentationDesignContent } from '../work-presentation-layouts';
2
- import type { WorkPresentationContent, WorkSlide, WorkSlideAnimation, WorkSlideElement } from '../work-types';
2
+ import { type WorkSlideAnimationCueItem } from '../work-presentation-animation';
3
+ import type { WorkPresentationContent, WorkSlide, WorkSlideElement } from '../work-types';
3
4
  export declare function SlideCanvas({ content, designContent, slide, interactive, aspectRatio, animationCueIndex, showPlaceholders, }: {
4
5
  content?: WorkPresentationContent;
5
6
  designContent?: WorkPresentationDesignContent;
@@ -21,9 +22,10 @@ export declare function EditableSlideTable({ element, onChange, }: {
21
22
  }): import("react").JSX.Element | null;
22
23
  export declare function slideElementStyle(element: WorkSlideElement): React.CSSProperties;
23
24
  interface SlideElementAnimationPlayback {
24
- animation: WorkSlideAnimation;
25
- startOffsetMs: number;
26
- state: 'finished' | 'pending' | 'playing';
25
+ animations: WorkSlideAnimationCueItem[];
26
+ finallyVisible: boolean;
27
+ initiallyVisible: boolean;
28
+ state: 'hidden' | 'playing' | 'visible';
27
29
  }
28
30
  export declare function slideTextStyle(element: WorkSlideElement): React.CSSProperties;
29
31
  export declare function SlideElementTextPreview({ element, showPlaceholder, }: {
@@ -1,11 +1,11 @@
1
- import type { WorkPresentationContent, WorkSlide, WorkSlideAnimation, WorkSlideAnimationEffect } from '../work-types';
1
+ import type { WorkPresentationContent, WorkSlide, WorkSlideAnimation, WorkSlideAnimationClass, WorkSlideAnimationEffect } from '../work-types';
2
2
  export interface PresentationAnimationCommands {
3
- canMoveEntranceAnimation: (direction: -1 | 1) => boolean;
4
- canSetEntranceAnimation: boolean;
5
- canUpdateEntranceAnimation: boolean;
6
- moveEntranceAnimation: (direction: -1 | 1) => boolean;
7
- setEntranceAnimation: (effect: WorkSlideAnimationEffect | undefined) => boolean;
8
- updateEntranceAnimation: (patch: Partial<WorkSlideAnimation>) => boolean;
3
+ canMoveAnimation: (animationClass: WorkSlideAnimationClass, direction: -1 | 1) => boolean;
4
+ canSetAnimation: boolean;
5
+ canUpdateAnimation: (animationClass: WorkSlideAnimationClass, patch: Partial<WorkSlideAnimation>) => boolean;
6
+ moveAnimation: (animationClass: WorkSlideAnimationClass, direction: -1 | 1) => boolean;
7
+ setAnimation: (animationClass: WorkSlideAnimationClass, effect: WorkSlideAnimationEffect | undefined) => boolean;
8
+ updateAnimation: (animationClass: WorkSlideAnimationClass, patch: Partial<WorkSlideAnimation>) => boolean;
9
9
  }
10
10
  export declare function usePresentationAnimationCommands({ content, onChange, selectedElementId, selectedSlide, }: {
11
11
  content: WorkPresentationContent;
@@ -1,4 +1,4 @@
1
- import type { WorkSlide, WorkSlideAnimation, WorkSlideAnimationEffect } from './work-types';
1
+ import type { WorkSlide, WorkSlideAnimation, WorkSlideAnimationClass, WorkSlideAnimationEffect } from './work-types';
2
2
  export { WORK_SLIDE_ANIMATION_LIMIT, WORK_SLIDE_ANIMATION_MAX_DELAY_MS, WORK_SLIDE_ANIMATION_MAX_DURATION_MS, WORK_SLIDE_ANIMATION_MIN_DURATION_MS, } from './work-presentation-animation-constraints';
3
3
  export interface WorkSlideAnimationCueItem {
4
4
  animation: WorkSlideAnimation;
@@ -10,11 +10,15 @@ export interface WorkSlideAnimationCue {
10
10
  items: WorkSlideAnimationCueItem[];
11
11
  totalDurationMs: number;
12
12
  }
13
+ export type WorkSlideAnimationSequenceIssue = 'duplicate-class' | 'overlapping-target';
13
14
  export declare function createWorkSlideAnimation(elementId: string, effect: WorkSlideAnimationEffect, previous?: WorkSlideAnimation): WorkSlideAnimation;
14
15
  export declare function normalizeWorkSlideAnimation(animation: WorkSlideAnimation): WorkSlideAnimation;
15
- export declare function workSlideAnimationForElement(slide: WorkSlide, elementId: string | undefined): WorkSlideAnimation | undefined;
16
+ export declare function workSlideAnimationClass(effect: WorkSlideAnimationEffect): WorkSlideAnimationClass;
17
+ export declare function workSlideAnimationEffectMatchesClass(effect: WorkSlideAnimationEffect, animationClass: WorkSlideAnimationClass): boolean;
18
+ export declare function workSlideAnimationForElement(slide: WorkSlide, elementId: string | undefined, animationClass: WorkSlideAnimationClass): WorkSlideAnimation | undefined;
16
19
  export declare function workSlideAnimationIndex(slide: WorkSlide, animationId: string | undefined): number;
17
20
  export declare function workSlideAnimationCues(animations: readonly WorkSlideAnimation[] | undefined): WorkSlideAnimationCue[];
18
21
  export declare function initialWorkSlideAnimationCueIndex(cues: readonly WorkSlideAnimationCue[]): number;
22
+ export declare function workSlideAnimationSequenceIssue(animations: readonly WorkSlideAnimation[] | undefined): WorkSlideAnimationSequenceIssue | undefined;
19
23
  export declare function removeWorkSlideAnimationsForElements(slide: WorkSlide, elementIds: ReadonlySet<string>): WorkSlide;
20
24
  export declare function remapWorkSlideAnimations(animations: readonly WorkSlideAnimation[] | undefined, elementIds: ReadonlyMap<string, string>): WorkSlideAnimation[] | undefined;
@@ -1,10 +1,6 @@
1
1
  import type { WorkSlide, WorkSlideAnimation, WorkSlideElement } from './work-types';
2
2
  export declare const PRESENTATION_OBJECT_OFFSET_STEP = 5;
3
3
  export type WorkPresentationClipboardPayload = {
4
- kind: 'element';
5
- element: WorkSlideElement;
6
- animation?: WorkSlideAnimation;
7
- } | {
8
4
  kind: 'elements';
9
5
  elements: WorkSlideElement[];
10
6
  animations?: WorkSlideAnimation[];
@@ -20,7 +16,6 @@ export interface WorkPresentationElementPaste {
20
16
  animations?: WorkSlideAnimation[];
21
17
  elements: WorkSlideElement[];
22
18
  }
23
- export declare function copyPresentationElement(element: WorkSlideElement, animation?: WorkSlideAnimation): void;
24
19
  export declare function copyPresentationElements(elements: readonly WorkSlideElement[], animations?: readonly WorkSlideAnimation[]): void;
25
20
  export declare function copyPresentationSlide(slide: WorkSlide): void;
26
21
  export declare function takePresentationClipboard(): WorkPresentationClipboardRead | null;
@@ -589,12 +589,16 @@ export interface WorkSlideTransition {
589
589
  advanceOnClick: boolean;
590
590
  advanceAfterMs?: number;
591
591
  }
592
- export type WorkSlideAnimationEffect = 'appear' | 'fade' | 'fly-in' | 'zoom';
592
+ export type WorkSlideAnimationClass = 'entrance' | 'exit';
593
+ export type WorkSlideEntranceAnimationEffect = 'appear' | 'fade' | 'fly-in' | 'zoom';
594
+ export type WorkSlideExitAnimationEffect = 'disappear' | 'fade-out' | 'fly-out' | 'zoom-out';
595
+ export type WorkSlideAnimationEffect = WorkSlideEntranceAnimationEffect | WorkSlideExitAnimationEffect;
593
596
  export type WorkSlideAnimationTrigger = 'on-click' | 'with-previous' | 'after-previous';
594
597
  export type WorkSlideAnimationDirection = 'left' | 'right' | 'up' | 'down';
595
598
  /**
596
- * One bounded entrance animation applied to one slide-owned element.
597
- * Array order on `WorkSlide.animations` is the canonical playback order.
599
+ * One bounded entrance or exit animation applied to one slide-owned element.
600
+ * An element may own at most one animation from each class. Array order on
601
+ * `WorkSlide.animations` is the canonical playback order.
598
602
  */
599
603
  export interface WorkSlideAnimation {
600
604
  id: string;
Binary file