@a3s-lab/office 0.33.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.
@@ -3,7 +3,7 @@ import { normalizeRadarStyle, presentationChartShowsLegend, presentationChartAxe
3
3
  import { directChildren, xmlNamespacePrefix, parseXml, firstDescendant, directChild, descendants, childPath, attribute } from "./4121.js";
4
4
  import { workSpreadsheetChartAxisShowsMajorGridlinesByDefault, workSpreadsheetChartAxisDefaultLabelPosition, workSpreadsheetChartAxisIsCategoryAxis, workSpreadsheetChartAxisIsValueAxis } from "./8715.js";
5
5
  import { presentationGroupPath, withPresentationDesign } from "./0~1403.js";
6
- import { patchPptxChartLayoutAndSeriesStyles, patchPptxTransitions, patchPptxComments, patchPptxChartDataLabels, patchPptxChartSeriesAnalysis } from "./0~7614.js";
6
+ import { patchPptxChartLayoutAndSeriesStyles, patchPptxTransitions, patchPptxComments, patchPptxChartDataLabels, patchPptxChartSeriesAnalysis, patchPptxAnimations } from "./0~7828.js";
7
7
  const DRAWING_NAMESPACE = 'http://schemas.openxmlformats.org/drawingml/2006/main';
8
8
  async function patchPptxChartAxes(buffer, slides) {
9
9
  const charts = slides.flatMap((slide)=>slide.elements.flatMap((element)=>'chart' === element.type && element.chart ? [
@@ -270,27 +270,40 @@ const PRESENTATIONML_NAMESPACE = 'http://schemas.openxmlformats.org/presentation
270
270
  const DRAWINGML_NAMESPACE = 'http://schemas.openxmlformats.org/drawingml/2006/main';
271
271
  const GROUPABLE_PART = /^ppt\/(?:slides|slideLayouts|slideMasters)\/[^/]+\.xml$/;
272
272
  class PptxGroupExportRegistry {
273
+ animatedElementKeys = new Set();
273
274
  bindings = new Map();
275
+ bindingsByElement = new Map();
274
276
  roleCounts = new Map();
275
277
  markerSequence = 0;
276
278
  get size() {
277
279
  return this.bindings.size;
278
280
  }
281
+ registerAnimatedElements(scope, elementIds) {
282
+ for (const elementId of elementIds)this.animatedElementKeys.add(exportElementKey(scope, elementId));
283
+ }
279
284
  objectName(scope, element, role) {
280
285
  const groupPath = presentationGroupPath(element);
281
- if (!groupPath.length) return;
286
+ const elementKey = exportElementKey(scope, element.id);
287
+ if (!groupPath.length && !this.animatedElementKeys.has(elementKey)) return;
288
+ const existing = this.bindingsByElement.get(elementKey);
289
+ if (existing) return existing.marker;
282
290
  const roleCount = (this.roleCounts.get(role) ?? 0) + 1;
283
291
  this.roleCounts.set(role, roleCount);
284
292
  this.markerSequence += 1;
285
293
  const marker = `${EXPORT_MARKER_PREFIX}${this.markerSequence}`;
286
- this.bindings.set(marker, {
294
+ const binding = {
287
295
  displayName: `${pptxRoleName(role)} ${roleCount}`,
288
296
  groupPath,
289
297
  groupScope: scope,
290
298
  marker
291
- });
299
+ };
300
+ this.bindings.set(marker, binding);
301
+ this.bindingsByElement.set(elementKey, binding);
292
302
  return marker;
293
303
  }
304
+ markerForElement(scope, elementId) {
305
+ return this.bindingsByElement.get(exportElementKey(scope, elementId))?.marker;
306
+ }
294
307
  binding(marker) {
295
308
  return this.bindings.get(marker);
296
309
  }
@@ -315,7 +328,7 @@ async function patchPptxNativeGroups(buffer, registry) {
315
328
  }
316
329
  for (const binding of registry.values()){
317
330
  const count = found.get(binding.marker) ?? 0;
318
- if (1 !== count) throw new Error(`PPTX group export expected one generated object for ${binding.displayName}, but found ${count}.`);
331
+ if (1 !== count) throw new Error(`PPTX export expected one generated object for ${binding.displayName}, but found ${count}.`);
319
332
  }
320
333
  return archive.generateAsync({
321
334
  type: 'arraybuffer',
@@ -323,6 +336,7 @@ async function patchPptxNativeGroups(buffer, registry) {
323
336
  });
324
337
  }
325
338
  function patchPptxShapeTree(shapeTree, registry, found) {
339
+ normalizePptxNonVisualIds(shapeTree);
326
340
  const sceneNodes = directChildren(shapeTree).filter(isPptxSceneNode).map((node, order)=>{
327
341
  const properties = firstDescendant(node, 'cNvPr');
328
342
  const objectName = properties ? attribute(properties, 'name') : null;
@@ -342,9 +356,9 @@ function patchPptxShapeTree(shapeTree, registry, found) {
342
356
  });
343
357
  if (!sceneNodes.some((item)=>item.binding)) return false;
344
358
  const roots = new Map();
345
- const rootNodes = sceneNodes.filter((item)=>!item.binding);
359
+ const rootNodes = sceneNodes.filter((item)=>!item.binding?.groupPath.length);
346
360
  for (const item of sceneNodes){
347
- if (!item.binding) continue;
361
+ if (!item.binding?.groupPath.length) continue;
348
362
  let siblings = roots;
349
363
  let bucket;
350
364
  for (const groupId of item.binding.groupPath){
@@ -378,7 +392,6 @@ function patchPptxShapeTree(shapeTree, registry, found) {
378
392
  ].sort((left, right)=>left.order - right.order);
379
393
  const extensionList = directChild(shapeTree, 'extLst') ?? null;
380
394
  for (const item of renderedRoots)shapeTree.insertBefore(item.node, extensionList);
381
- normalizePptxNonVisualIds(shapeTree);
382
395
  return true;
383
396
  }
384
397
  function renderPptxGroup(bucket, document, nextId, nextGroupNumber) {
@@ -492,6 +505,9 @@ function normalizePptxNonVisualIds(shapeTree) {
492
505
  function groupBucketKey(scope, groupId) {
493
506
  return `${scope}\u0000${groupId}`;
494
507
  }
508
+ function exportElementKey(scope, elementId) {
509
+ return `${scope}\u0000${elementId}`;
510
+ }
495
511
  function isPptxSceneNode(node) {
496
512
  return [
497
513
  'contentPart',
@@ -735,7 +751,9 @@ function createPptxExportState(artifact, PptxGenJS) {
735
751
  if (!source.useLayoutBackground) slide.background = {
736
752
  color: source.background.replace('#', '')
737
753
  };
738
- for (const element of source.elements)addPresentationElement(slide, element, presentation, slideWidth, slideHeight, groups, `slide:${source.id}`, element.placeholder ? binding?.placeholderNames.get(element.placeholder.key) : void 0);
754
+ const groupScope = `slide:${source.id}`;
755
+ groups.registerAnimatedElements(groupScope, source.animations?.map((animation)=>animation.elementId) ?? []);
756
+ for (const element of source.elements)addPresentationElement(slide, element, presentation, slideWidth, slideHeight, groups, groupScope, element.placeholder ? binding?.placeholderNames.get(element.placeholder.key) : void 0);
739
757
  if (source.notes?.trim()) slide.addNotes(source.notes);
740
758
  }
741
759
  return {
@@ -758,7 +776,8 @@ async function createPptxBlob(artifact, PptxGenJS) {
758
776
  const withChartSeriesAnalysis = await patchPptxChartSeriesAnalysis(withChartAxes, slides);
759
777
  const withTransitions = await patchPptxTransitions(withChartSeriesAnalysis, slides);
760
778
  const withComments = await patchPptxComments(withTransitions, slides, 'presentation' === artifact.content.type ? artifact.content.width ?? 13.333 : 13.333, 'presentation' === artifact.content.type ? artifact.content.height ?? 7.5 : 7.5);
761
- const patched = await patchPptxNativeGroups(withComments, groups);
779
+ const withAnimations = await patchPptxAnimations(withComments, slides, groups);
780
+ const patched = await patchPptxNativeGroups(withAnimations, groups);
762
781
  return new Blob([
763
782
  patched
764
783
  ], {
@@ -886,8 +905,13 @@ function addPresentationElement(slide, element, presentation, slideWidth, slideH
886
905
  });
887
906
  return;
888
907
  }
889
- if ('shape' === element.type) addShape(slide, element, presentation, x, y, width, height, resolvedPlaceholder, groups.objectName(groupScope, element, 'shape'));
890
- if (element.text) addText(slide, element, x, y, width, height, resolvedPlaceholder, groups.objectName(groupScope, element, 'text'));
908
+ if ('shape' === element.type) {
909
+ const objectName = groups.objectName(groupScope, element, 'shape');
910
+ if (element.text || element.textRuns?.length) addText(slide, element, x, y, width, height, resolvedPlaceholder, objectName, pptxShapeType(presentation, element));
911
+ else addShape(slide, element, presentation, x, y, width, height, resolvedPlaceholder, objectName);
912
+ return;
913
+ }
914
+ if ('text' === element.type) addText(slide, element, x, y, width, height, resolvedPlaceholder, groups.objectName(groupScope, element, 'text'));
891
915
  }
892
916
  function pptxChartData(chart) {
893
917
  if (presentationChartUsesNumericXAxis(chart.type)) return [
@@ -926,7 +950,7 @@ function pptxLegendPosition(position) {
926
950
  return 'r';
927
951
  }
928
952
  function addShape(slide, element, presentation, x, y, width, height, placeholder, objectName) {
929
- const shapeType = 'ellipse' === element.shapeType ? presentation.ShapeType.ellipse : 'triangle' === element.shapeType ? presentation.ShapeType.triangle : 'diamond' === element.shapeType ? presentation.ShapeType.diamond : 'roundRect' === element.shapeType ? presentation.ShapeType.roundRect : presentation.ShapeType.rect;
953
+ const shapeType = pptxShapeType(presentation, element);
930
954
  const fillColor = element.fill.replace('#', '');
931
955
  const borderColor = (element.borderColor ?? element.fill).replace('#', '');
932
956
  const options = {
@@ -954,7 +978,7 @@ function addShape(slide, element, presentation, x, y, width, height, placeholder
954
978
  };
955
979
  slide.addShape(shapeType, options);
956
980
  }
957
- function addText(slide, element, x, y, width, height, placeholder, objectName) {
981
+ function addText(slide, element, x, y, width, height, placeholder, objectName, shape) {
958
982
  const text = element.textRuns?.length ? element.textRuns.map((run)=>({
959
983
  text: run.text,
960
984
  options: {
@@ -989,9 +1013,14 @@ function addText(slide, element, x, y, width, height, placeholder, objectName) {
989
1013
  valign: element.verticalAlign ?? 'middle',
990
1014
  margin: 'transparent' === element.fill ? 0 : 10,
991
1015
  breakLine: false,
1016
+ shape,
992
1017
  fill: 'transparent' === element.fill ? void 0 : {
993
1018
  color: element.fill.replace('#', '')
994
1019
  },
1020
+ line: element.borderWidth && element.borderColor ? {
1021
+ color: element.borderColor.replace('#', ''),
1022
+ width: element.borderWidth
1023
+ } : void 0,
995
1024
  hyperlink: element.href ? {
996
1025
  url: element.href
997
1026
  } : void 0,
@@ -1001,6 +1030,13 @@ function addText(slide, element, x, y, width, height, placeholder, objectName) {
1001
1030
  } : {}
1002
1031
  });
1003
1032
  }
1033
+ function pptxShapeType(presentation, element) {
1034
+ if ('ellipse' === element.shapeType) return presentation.ShapeType.ellipse;
1035
+ if ('triangle' === element.shapeType) return presentation.ShapeType.triangle;
1036
+ if ('diamond' === element.shapeType) return presentation.ShapeType.diamond;
1037
+ if ('roundRect' === element.shapeType) return presentation.ShapeType.roundRect;
1038
+ return presentation.ShapeType.rect;
1039
+ }
1004
1040
  async function presentationArrayBuffer(output) {
1005
1041
  if (output instanceof ArrayBuffer) return output;
1006
1042
  if (output instanceof Blob) return output.arrayBuffer();
@@ -1,6 +1,6 @@
1
1
  import { contentTypeForPart, directChildren, bytesToDataUrl, OoxmlPackage, firstDescendant, descendants, directChild, childPath, attribute, createOfficeId as createWorkId } from "./4121.js";
2
2
  import { normalizeRadarStyle, normalizePresentationChartLegendPosition, presentationChartUsesNumericXAxis, normalizePresentationBubbleScale, normalizePresentationBubbleSizeRepresents, withPresentationChartLayout, normalizeDoughnutHoleSize, normalizePresentationScatterStyle } from "./0~work-presentation-charts.js";
3
- import { loadPptxCommentAuthors, readPptxSlideComments, readPptxChartLayoutAndSeriesStyles, readPptxTransition, readPptxChartSeriesAnalysis, readPptxChartDataLabels } from "./0~7614.js";
3
+ import { loadPptxCommentAuthors, readPptxSlideComments, readPptxChartLayoutAndSeriesStyles, readPptxAnimations, readPptxTransition, readPptxChartSeriesAnalysis, readPptxChartDataLabels } from "./0~7828.js";
4
4
  import { parseXlsxChartAxes } from "./4166.js";
5
5
  import { scaledPresentationVisuals } from "./0~work-presentation-visual-scale.js";
6
6
  async function loadPptxTheme(archive, presentationRelationships) {
@@ -547,6 +547,7 @@ async function parseSlide(archive, slidePart, slideNumber, slideWidthEmu, slideH
547
547
  theme,
548
548
  issues,
549
549
  imageBudget,
550
+ elementIdByPptxShapeId: new Map(),
550
551
  location: `幻灯片 ${slideNumber}`
551
552
  };
552
553
  const design = await ensurePptxDesignDefinitions(archive, inheritance.layout, inheritance.master, context, designs);
@@ -554,6 +555,7 @@ async function parseSlide(archive, slidePart, slideNumber, slideWidthEmu, slideH
554
555
  const shapeTree = firstDescendant(document1, 'spTree');
555
556
  for (const child of shapeTree ? directChildren(shapeTree) : [])elements.push(...await parseSlideNode(child, context));
556
557
  const resolvedElements = inheritPptxPlaceholderStyles(elements, design.master, design.layout);
558
+ const animationResult = readPptxAnimations(document1, context.elementIdByPptxShapeId);
557
559
  const transition = readPptxTransition(document1);
558
560
  const commentResult = await readPptxSlideComments(archive, relationships, commentAuthors, slideNumber, slideWidthEmu, slideHeightEmu);
559
561
  if (commentResult.comments.length) addIssue(context, 'pptx.comments', 'Comments', `${commentResult.comments.length} traditional slide comment(s), authors, dates, and positions are preserved and editable.`, 'info');
@@ -564,7 +566,8 @@ async function parseSlide(archive, slidePart, slideNumber, slideWidthEmu, slideH
564
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');
565
567
  for (const diagnostic of transition.diagnostics)addIssue(context, diagnostic.code, 'Slide transitions', diagnostic.message);
566
568
  }
567
- if (firstDescendant(document1, 'timing')) addIssue(context, 'pptx.animation', 'Animations', 'Object animations are not replayed and will be omitted on export.');
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');
570
+ for (const diagnostic of animationResult.diagnostics)addIssue(context, diagnostic.code, 'Animations', diagnostic.message);
568
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.');
569
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.');
570
573
  const slideBackground = readPptxBackground(document1, theme);
@@ -578,7 +581,8 @@ async function parseSlide(archive, slidePart, slideNumber, slideWidthEmu, slideH
578
581
  elements: resolvedElements,
579
582
  notes: await readSpeakerNotes(archive, relationships, context),
580
583
  comments: commentResult.comments.length ? commentResult.comments : void 0,
581
- transition: transition.transition
584
+ transition: transition.transition,
585
+ animations: animationResult.animations.length ? animationResult.animations : void 0
582
586
  };
583
587
  }
584
588
  async function loadPptxPresentationDesigns(archive, relationships, slideWidthEmu, slideHeightEmu, theme, issues, imageBudget, registry) {
@@ -592,6 +596,7 @@ async function loadPptxPresentationDesigns(archive, relationships, slideWidthEmu
592
596
  theme,
593
597
  issues,
594
598
  imageBudget,
599
+ elementIdByPptxShapeId: new Map(),
595
600
  location: '母版与布局'
596
601
  };
597
602
  for (const relationship of relationships.values()){
@@ -659,6 +664,7 @@ function ensureFallbackPptxMaster(registry) {
659
664
  async function parsePptxDesignElements(part, context) {
660
665
  const partContext = {
661
666
  ...context,
667
+ elementIdByPptxShapeId: new Map(),
662
668
  relationships: part.relationships,
663
669
  placeholders: new Map()
664
670
  };
@@ -712,24 +718,30 @@ function inheritPptxPlaceholderStyle(element, definition) {
712
718
  };
713
719
  }
714
720
  async function parseSlideNode(node, context, transform) {
715
- if ('sp' === node.localName) return [
721
+ let elements;
722
+ if ('sp' === node.localName) elements = [
716
723
  withImportedGroupTransform(parseShape(node, context, transform), transform)
717
724
  ];
718
- if ('pic' === node.localName) return [
725
+ else if ('pic' === node.localName) elements = [
719
726
  withImportedGroupTransform(await parsePicture(node, context, transform), transform)
720
727
  ];
721
- if ('graphicFrame' === node.localName) return [
728
+ else if ('graphicFrame' === node.localName) elements = [
722
729
  withImportedGroupTransform(await parseGraphicFrame(node, context, transform), transform)
723
730
  ];
724
- if ('cxnSp' === node.localName) return [
731
+ else if ('cxnSp' === node.localName) elements = [
725
732
  withImportedGroupTransform(parseConnector(node, context, transform), transform)
726
733
  ];
727
- if ('grpSp' === node.localName) return parseGroup(node, context, transform);
734
+ else {
735
+ if ('grpSp' === node.localName) return parseGroup(node, context, transform);
736
+ elements = [];
737
+ }
728
738
  if ([
729
739
  'contentPart',
730
740
  'oleObj'
731
741
  ].includes(node.localName)) addIssue(context, 'pptx.content-part', 'Embedded content', 'An embedded slide object remains available in the original PPTX only.');
732
- return [];
742
+ const pptxShapeId = attribute(firstDescendant(node, 'cNvPr') ?? node, 'id');
743
+ if (pptxShapeId && 1 === elements.length) context.elementIdByPptxShapeId.set(pptxShapeId, elements[0].id);
744
+ return elements;
733
745
  }
734
746
  function parseShape(node, context, transform) {
735
747
  const box = elementBox(node, context, transform);
package/dist/4121.js CHANGED
@@ -2918,6 +2918,13 @@ const WORK_TEMPLATES = [
2918
2918
  name: '策略汇报',
2919
2919
  description: '结论先行的三页汇报',
2920
2920
  accent: '#c85637'
2921
+ },
2922
+ {
2923
+ id: 'animated-deck',
2924
+ kind: 'presentation',
2925
+ name: '入场动画',
2926
+ description: '出现、淡入、飞入、缩放与三种触发方式',
2927
+ accent: '#7657c8'
2921
2928
  }
2922
2929
  ];
2923
2930
  function createWorkArtifact(templateId) {
@@ -2951,7 +2958,8 @@ function initialTitle(templateId, kind) {
2951
2958
  'proofing-languages': '校对语言示例',
2952
2959
  'quarterly-plan': '季度执行计划',
2953
2960
  'data-validation': '数据验证示例',
2954
- 'strategy-deck': '业务策略汇报'
2961
+ 'strategy-deck': '业务策略汇报',
2962
+ 'animated-deck': '入场动画示例'
2955
2963
  };
2956
2964
  if (titles[templateId]) return titles[templateId];
2957
2965
  if ('document' === kind) return '无标题文字';
@@ -3250,6 +3258,7 @@ function contentForTemplate(templateId) {
3250
3258
  sheets: dataValidationTemplateSheets()
3251
3259
  };
3252
3260
  if ('strategy-deck' === templateId) return strategyPresentation();
3261
+ if ('animated-deck' === templateId) return animatedPresentation();
3253
3262
  if ('blank-spreadsheet' === templateId) return {
3254
3263
  type: 'spreadsheet',
3255
3264
  sheets: [
@@ -3836,6 +3845,118 @@ function strategyPresentation() {
3836
3845
  slides
3837
3846
  };
3838
3847
  }
3848
+ function animatedPresentation() {
3849
+ const titleId = createWorkId('element');
3850
+ const subtitleId = createWorkId('element');
3851
+ const sequenceId = createWorkId('element');
3852
+ const conclusionId = createWorkId('element');
3853
+ return {
3854
+ type: 'presentation',
3855
+ slides: [
3856
+ {
3857
+ id: createWorkId('slide'),
3858
+ name: '入场动画',
3859
+ background: '#17162b',
3860
+ notes: '从“动画”选项卡编辑效果、触发方式、持续时间、延迟和顺序,再从头放映验证逐步播放。',
3861
+ elements: [
3862
+ {
3863
+ id: titleId,
3864
+ type: 'text',
3865
+ x: 8,
3866
+ y: 10,
3867
+ width: 76,
3868
+ height: 16,
3869
+ text: '让信息按叙事顺序出现',
3870
+ fontSize: 34,
3871
+ color: '#ffffff',
3872
+ fill: 'transparent',
3873
+ bold: true,
3874
+ align: 'left'
3875
+ },
3876
+ {
3877
+ id: subtitleId,
3878
+ type: 'text',
3879
+ x: 8,
3880
+ y: 28,
3881
+ width: 68,
3882
+ height: 9,
3883
+ text: '单击开始,下一对象与标题同时淡入',
3884
+ fontSize: 16,
3885
+ color: '#b9b7dc',
3886
+ fill: 'transparent',
3887
+ bold: false,
3888
+ align: 'left'
3889
+ },
3890
+ {
3891
+ id: sequenceId,
3892
+ type: 'shape',
3893
+ x: 8,
3894
+ y: 46,
3895
+ width: 52,
3896
+ height: 20,
3897
+ text: '上一动画完成后,从左侧飞入',
3898
+ fontSize: 19,
3899
+ color: '#211f35',
3900
+ fill: '#d9d3ff',
3901
+ bold: true,
3902
+ align: 'center',
3903
+ radius: 4
3904
+ },
3905
+ {
3906
+ id: conclusionId,
3907
+ type: 'shape',
3908
+ x: 66,
3909
+ y: 46,
3910
+ width: 26,
3911
+ height: 20,
3912
+ text: '再次单击\n缩放结论',
3913
+ fontSize: 17,
3914
+ color: '#ffffff',
3915
+ fill: '#7657c8',
3916
+ bold: true,
3917
+ align: 'center',
3918
+ radius: 5
3919
+ }
3920
+ ],
3921
+ animations: [
3922
+ {
3923
+ id: createWorkId('slide-animation'),
3924
+ elementId: titleId,
3925
+ effect: 'appear',
3926
+ trigger: 'on-click',
3927
+ durationMs: 300,
3928
+ delayMs: 0
3929
+ },
3930
+ {
3931
+ id: createWorkId('slide-animation'),
3932
+ elementId: subtitleId,
3933
+ effect: 'fade',
3934
+ trigger: 'with-previous',
3935
+ durationMs: 600,
3936
+ delayMs: 100
3937
+ },
3938
+ {
3939
+ id: createWorkId('slide-animation'),
3940
+ elementId: sequenceId,
3941
+ effect: 'fly-in',
3942
+ trigger: 'after-previous',
3943
+ durationMs: 700,
3944
+ delayMs: 150,
3945
+ direction: 'left'
3946
+ },
3947
+ {
3948
+ id: createWorkId('slide-animation'),
3949
+ elementId: conclusionId,
3950
+ effect: 'zoom',
3951
+ trigger: 'on-click',
3952
+ durationMs: 650,
3953
+ delayMs: 0
3954
+ }
3955
+ ]
3956
+ }
3957
+ ]
3958
+ };
3959
+ }
3839
3960
  const OFFICE_KERNEL_SPREADSHEET_MAX_ROWS = 1048576;
3840
3961
  const spreadsheetErrors = new Set([
3841
3962
  '#BLOCKED!',
package/dist/4560.js CHANGED
@@ -44,8 +44,37 @@ function validateSlide(value) {
44
44
  slide.elements = validateRecords(record.elements, `element in slide '${slide.id}'`, validateElement);
45
45
  if (void 0 !== record.layoutId) slide.layoutId = requiredIdentifier(record.layoutId, 'slide layout');
46
46
  if (void 0 !== record.comments) slide.comments = validateRecords(record.comments, `comment in slide '${slide.id}'`, validateComment);
47
+ if (void 0 !== record.animations) slide.animations = validateSlideAnimations(record.animations, slide);
47
48
  return slide;
48
49
  }
50
+ function validateSlideAnimations(value, slide) {
51
+ if (!Array.isArray(value) || value.length > 256) invalidWorkOfficePresentationInput("an array of at most 256 slide animations");
52
+ const animationIds = new Set();
53
+ const targetIds = new Set();
54
+ const elementIds = new Set(slide.elements.map((element)=>element.id));
55
+ return value.map((item)=>{
56
+ const record = requiredInputRecord(item, 'slide animation');
57
+ const animation = validateJsonRecord(record, 'slide animation');
58
+ animation.id = requiredIdentifier(record.id, 'slide animation');
59
+ animation.elementId = requiredIdentifier(record.elementId, 'slide animation element');
60
+ if (animationIds.has(animation.id)) invalidWorkOfficePresentationInput(`a unique slide animation ID; '${animation.id}' is repeated`);
61
+ 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');
64
+ animation.effect = record.effect;
65
+ if ('on-click' !== record.trigger && 'with-previous' !== record.trigger && 'after-previous' !== record.trigger) invalidWorkOfficePresentationInput('a supported slide animation trigger');
66
+ animation.trigger = record.trigger;
67
+ animation.durationMs = requiredIntegerInRange(record.durationMs, 100, 60000, 'slide animation duration');
68
+ 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');
71
+ animation.direction = record.direction;
72
+ } else if (void 0 !== record.direction) invalidWorkOfficePresentationInput('slide animation directions only on fly-in effects');
73
+ animationIds.add(animation.id);
74
+ targetIds.add(animation.elementId);
75
+ return animation;
76
+ });
77
+ }
49
78
  function validateMaster(value) {
50
79
  const record = requiredInputRecord(value, 'presentation master');
51
80
  const master = validateJsonRecord(record, 'presentation master');
@@ -129,6 +158,10 @@ function requiredFiniteNumber(value, label) {
129
158
  if ('number' != typeof value || !Number.isFinite(value)) invalidWorkOfficePresentationInput(`a finite number for ${label}`);
130
159
  return value;
131
160
  }
161
+ function requiredIntegerInRange(value, minimum, maximum, label) {
162
+ if ('number' != typeof value || !Number.isSafeInteger(value) || value < minimum || value > maximum) invalidWorkOfficePresentationInput(`an integer ${label} from ${minimum} through ${maximum}`);
163
+ return value;
164
+ }
132
165
  function invalidWorkOfficePresentationInput(expected) {
133
166
  throw new WorkOfficeCollaborationError('office.collaboration.content_invalid', `Presentation collaboration requires ${expected}.`);
134
167
  }
@@ -0,0 +1,11 @@
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;
5
+ canPreview: boolean;
6
+ editable: boolean;
7
+ onMove: (direction: -1 | 1) => void;
8
+ onPreview: () => void;
9
+ onSetEffect: (effect: WorkSlideAnimationEffect | undefined) => void;
10
+ onUpdate: (patch: Partial<WorkSlideAnimation>) => void;
11
+ }): import("react").JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import type { OfficeKernelPresentationAlignment } from '../../../kernel/office-kernel-protocol';
2
- import type { WorkPresentationContent, WorkSlideElement, WorkSlideTransition, WorkSlideTextRun } from '../work-types';
2
+ import type { WorkPresentationContent, WorkSlideAnimation, 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,9 +37,11 @@ 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
41
  nudgeSelection: (key: string, distance: number) => boolean;
41
42
  openComment: (commentId: string) => PresentationCommandResult;
42
43
  pasteSelection: () => boolean;
44
+ previewAnimations: () => PresentationCommandResult;
43
45
  redo: () => boolean;
44
46
  renamePresentationLayout: (name: string) => PresentationCommandResult;
45
47
  renamePresentationMaster: (name: string) => PresentationCommandResult;
@@ -50,6 +52,7 @@ export interface PresentationEditorCommands {
50
52
  selectSlide: (slideId: string, returnToSlideMode?: boolean) => PresentationCommandResult;
51
53
  setPresentationContent: (content: WorkPresentationContent) => PresentationCommandResult;
52
54
  setBackground: (color: string) => PresentationCommandResult;
55
+ setEntranceAnimation: (effect: WorkSlideAnimationEffect | undefined) => PresentationCommandResult;
53
56
  setPresentationLayoutBackground: (color: string | undefined) => PresentationCommandResult;
54
57
  setPresentationMasterBackground: (color: string) => PresentationCommandResult;
55
58
  setTransition: (transition: WorkSlideTransition | undefined) => PresentationCommandResult;
@@ -66,6 +69,7 @@ export interface PresentationEditorCommands {
66
69
  updateElement: (patch: Partial<WorkSlideElement>, options?: {
67
70
  restoreTextFocus?: boolean;
68
71
  }) => PresentationCommandResult;
72
+ updateEntranceAnimation: (patch: Partial<WorkSlideAnimation>) => PresentationCommandResult;
69
73
  updateNotes: (notes: string) => PresentationCommandResult;
70
74
  updatePresentationComment: (slideId: string, commentId: string, text: string) => PresentationCommandResult;
71
75
  updateTextElement: (elementId: string, value: {
@@ -107,6 +111,16 @@ export interface PresentationSlideCommandPort {
107
111
  setTransition: (transition: WorkSlideTransition | undefined) => PresentationCommandResult;
108
112
  updateNotes: (notes: string) => PresentationCommandResult;
109
113
  }
114
+ export interface PresentationAnimationCommandPort {
115
+ canMoveEntranceAnimation: (direction: -1 | 1) => boolean;
116
+ canPreviewAnimations: boolean;
117
+ canSetEntranceAnimation: boolean;
118
+ canUpdateEntranceAnimation: boolean;
119
+ moveEntranceAnimation: (direction: -1 | 1) => PresentationCommandResult;
120
+ previewAnimations: () => PresentationCommandResult;
121
+ setEntranceAnimation: (effect: WorkSlideAnimationEffect | undefined) => PresentationCommandResult;
122
+ updateEntranceAnimation: (patch: Partial<WorkSlideAnimation>) => PresentationCommandResult;
123
+ }
110
124
  export interface PresentationInsertCommandPort {
111
125
  enabled: boolean;
112
126
  addChart: () => PresentationCommandResult;
@@ -192,6 +206,7 @@ export interface PresentationKeyboardCommandPort {
192
206
  selectedElementCount: number;
193
207
  }
194
208
  export interface PresentationCommandContext {
209
+ animations: PresentationAnimationCommandPort;
195
210
  clipboard: PresentationClipboardCommandPort;
196
211
  design: PresentationDesignCommandPort;
197
212
  document: PresentationDocumentCommandPort;
@@ -4,7 +4,8 @@ export interface PresentationTimerController {
4
4
  runningSinceMilliseconds: number | null;
5
5
  }
6
6
  export declare function createPresentationTimerController(now?: number): PresentationTimerController;
7
- export declare function PresentationPresenterView({ content, slide, nextSlide, index, total, aspectRatio, timer, }: {
7
+ export declare function PresentationPresenterView({ animationCueIndex, content, slide, nextSlide, index, total, aspectRatio, timer, }: {
8
+ animationCueIndex?: number;
8
9
  content: WorkPresentationContent;
9
10
  slide: WorkSlide;
10
11
  nextSlide?: WorkSlide;
@@ -1,14 +1,16 @@
1
1
  import { type WorkPresentationDesignContent } from '../work-presentation-layouts';
2
- import type { WorkPresentationContent, WorkSlide, WorkSlideElement } from '../work-types';
3
- export declare function SlideCanvas({ content, designContent, slide, interactive, aspectRatio, showPlaceholders, }: {
2
+ import type { WorkPresentationContent, WorkSlide, WorkSlideAnimation, WorkSlideElement } from '../work-types';
3
+ export declare function SlideCanvas({ content, designContent, slide, interactive, aspectRatio, animationCueIndex, showPlaceholders, }: {
4
4
  content?: WorkPresentationContent;
5
5
  designContent?: WorkPresentationDesignContent;
6
6
  slide: WorkSlide;
7
7
  interactive: boolean;
8
8
  aspectRatio: string;
9
+ animationCueIndex?: number;
9
10
  showPlaceholders?: boolean;
10
11
  }): import("react").JSX.Element;
11
- export declare function SlideElementPreview({ element, origin, showPlaceholder, }: {
12
+ export declare function SlideElementPreview({ animationPlayback, element, origin, showPlaceholder, }: {
13
+ animationPlayback?: SlideElementAnimationPlayback;
12
14
  element: WorkSlideElement;
13
15
  origin: 'inherited' | 'slide';
14
16
  showPlaceholder?: boolean;
@@ -18,6 +20,11 @@ export declare function EditableSlideTable({ element, onChange, }: {
18
20
  onChange: (rows: string[][]) => void;
19
21
  }): import("react").JSX.Element | null;
20
22
  export declare function slideElementStyle(element: WorkSlideElement): React.CSSProperties;
23
+ interface SlideElementAnimationPlayback {
24
+ animation: WorkSlideAnimation;
25
+ startOffsetMs: number;
26
+ state: 'finished' | 'pending' | 'playing';
27
+ }
21
28
  export declare function slideTextStyle(element: WorkSlideElement): React.CSSProperties;
22
29
  export declare function SlideElementTextPreview({ element, showPlaceholder, }: {
23
30
  element: WorkSlideElement;
@@ -26,3 +33,4 @@ export declare function SlideElementTextPreview({ element, showPlaceholder, }: {
26
33
  export declare function SlideTablePreview({ element }: {
27
34
  element: WorkSlideElement;
28
35
  }): import("react").JSX.Element | null;
36
+ export {};
@@ -0,0 +1,15 @@
1
+ import type { WorkPresentationContent, WorkSlide, WorkSlideAnimation, WorkSlideAnimationEffect } from '../work-types';
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;
9
+ }
10
+ export declare function usePresentationAnimationCommands({ content, onChange, selectedElementId, selectedSlide, }: {
11
+ content: WorkPresentationContent;
12
+ onChange: (content: WorkPresentationContent) => void;
13
+ selectedElementId: string | undefined;
14
+ selectedSlide: WorkSlide;
15
+ }): PresentationAnimationCommands;
@@ -0,0 +1,13 @@
1
+ import type { PptxGroupExportRegistry } from './work-pptx-groups';
2
+ import type { WorkSlide, WorkSlideAnimation } from './work-types';
3
+ interface PptxAnimationDiagnostic {
4
+ code: string;
5
+ message: string;
6
+ }
7
+ interface PptxAnimationReadResult {
8
+ animations: WorkSlideAnimation[];
9
+ diagnostics: PptxAnimationDiagnostic[];
10
+ }
11
+ export declare function readPptxAnimations(document: Document, elementIdByPptxShapeId: ReadonlyMap<string, string>): PptxAnimationReadResult;
12
+ export declare function patchPptxAnimations(buffer: ArrayBuffer, slides: readonly WorkSlide[], registry: PptxGroupExportRegistry): Promise<ArrayBuffer>;
13
+ export {};
@@ -7,11 +7,15 @@ interface PptxExportBinding {
7
7
  marker: string;
8
8
  }
9
9
  export declare class PptxGroupExportRegistry {
10
+ private readonly animatedElementKeys;
10
11
  private readonly bindings;
12
+ private readonly bindingsByElement;
11
13
  private readonly roleCounts;
12
14
  private markerSequence;
13
15
  get size(): number;
16
+ registerAnimatedElements(scope: string, elementIds: Iterable<string>): void;
14
17
  objectName(scope: string, element: WorkSlideElement, role: PptxExportObjectRole): string | undefined;
18
+ markerForElement(scope: string, elementId: string): string | undefined;
15
19
  binding(marker: string): PptxExportBinding | undefined;
16
20
  values(): IterableIterator<PptxExportBinding>;
17
21
  }
@@ -0,0 +1,4 @@
1
+ export declare const WORK_SLIDE_ANIMATION_LIMIT = 256;
2
+ export declare const WORK_SLIDE_ANIMATION_MIN_DURATION_MS = 100;
3
+ export declare const WORK_SLIDE_ANIMATION_MAX_DURATION_MS = 60000;
4
+ export declare const WORK_SLIDE_ANIMATION_MAX_DELAY_MS = 60000;