jvs-ppt-ui 0.0.14 → 0.0.16

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 (67) hide show
  1. package/dist/lib/check_imgs.html +18 -0
  2. package/dist/lib/jvs-ppt-ui/iconfont/iconfont.css +187 -27
  3. package/dist/lib/jvs-ppt-ui/iconfont/iconfont.js +1 -1
  4. package/dist/lib/jvs-ppt-ui/iconfont/iconfont.json +345 -65
  5. package/dist/lib/jvs-ppt-ui/iconfont/iconfont.ttf +0 -0
  6. package/dist/lib/jvs-ppt-ui/iconfont/iconfont.woff +0 -0
  7. package/dist/lib/jvs-ppt-ui/iconfont/iconfont.woff2 +0 -0
  8. package/dist/lib/jvs-ppt-ui/img/al.png +0 -0
  9. package/dist/lib/jvs-ppt-ui/img/d.png +0 -0
  10. package/dist/lib/jvs-ppt-ui/img/dl.png +0 -0
  11. package/dist/lib/jvs-ppt-ui/img/l.png +0 -0
  12. package/dist/lib/jvs-ppt-ui.es.js +44973 -43349
  13. package/dist/lib/jvs-ppt-ui.umd.js +102 -102
  14. package/dist/lib/style.css +1 -1
  15. package/package.json +1 -1
  16. package/scripts/check-icons.mjs +22 -0
  17. package/scripts/patch-anim-icons.mjs +45 -0
  18. package/src/assets/iconfont/iconfont.css +187 -27
  19. package/src/assets/iconfont/iconfont.js +1 -1
  20. package/src/assets/iconfont/iconfont.json +345 -65
  21. package/src/assets/iconfont/iconfont.ttf +0 -0
  22. package/src/assets/iconfont/iconfont.woff +0 -0
  23. package/src/assets/iconfont/iconfont.woff2 +0 -0
  24. package/src/components/CanvasArea.vue +218 -193
  25. package/src/components/ChartTypeSelector.vue +509 -148
  26. package/src/components/Header.vue +327 -44
  27. package/src/components/PresentationGroupChild.vue +44 -13
  28. package/src/components/PresentationMode.vue +84 -33
  29. package/src/components/QuickBorderPopover.vue +51 -11
  30. package/src/components/QuickColorPopover.vue +65 -21
  31. package/src/components/SidebarLeft.vue +286 -107
  32. package/src/components/SidebarRight.vue +45 -38
  33. package/src/components/TemplateGallery.vue +1110 -1237
  34. package/src/components/ToastNotification.vue +2 -2
  35. package/src/components/Toolbar.vue +825 -733
  36. package/src/components/canvas/CanvasQuickToolbar.vue +17 -30
  37. package/src/components/canvas/elements/CanvasGroupElement.vue +6 -0
  38. package/src/components/canvas/elements/CanvasShapeElement.vue +198 -38
  39. package/src/components/canvas/elements/CanvasTextElement.vue +4 -3
  40. package/src/components/sidebar/common/SidebarAlignGroup.vue +104 -50
  41. package/src/components/sidebar/common/SidebarBorderAccordion.vue +375 -148
  42. package/src/components/sidebar/common/SidebarPositionGroup.vue +358 -149
  43. package/src/components/sidebar/common/SidebarReflectionAccordion.vue +429 -203
  44. package/src/components/sidebar/common/SidebarShadowAccordion.vue +844 -416
  45. package/src/components/sidebar/panels/GradientEditor.vue +671 -316
  46. package/src/components/sidebar/panels/LayoutThumbSvg.vue +33 -16
  47. package/src/components/sidebar/panels/SidebarAnimationPanel.vue +701 -138
  48. package/src/components/sidebar/panels/SidebarChartFormatPanel.vue +3 -3
  49. package/src/components/sidebar/panels/SidebarDesignPanel.vue +1754 -416
  50. package/src/components/sidebar/panels/SidebarMediaFormatPanel.vue +12 -0
  51. package/src/components/sidebar/panels/SidebarShapeFormatPanel.vue +813 -174
  52. package/src/components/sidebar/panels/SidebarTableFormatPanel.vue +2 -2
  53. package/src/components/sidebar/panels/SidebarTextFormatPanel.vue +1938 -1427
  54. package/src/components/sidebar/panels/SidebarTransitionPanel.vue +791 -210
  55. package/src/composables/canvas/useCanvasShortcuts.ts +20 -6
  56. package/src/composables/canvas/useCanvasText.ts +1 -1
  57. package/src/composables/canvas/useCanvasTransform.ts +44 -0
  58. package/src/composables/presetThemes.ts +43 -44
  59. package/src/composables/sidebar/useSidebarAnimation.ts +101 -25
  60. package/src/composables/sidebar/useSidebarDesign.ts +30 -11
  61. package/src/composables/sidebar/useSidebarElementTransform.ts +10 -1
  62. package/src/composables/sidebar/useSidebarImage.ts +10 -2
  63. package/src/composables/sidebar/useSidebarShapeFormat.ts +562 -107
  64. package/src/composables/useSlides.ts +331 -153
  65. package/src/main.ts +2 -0
  66. package/src/style.css +926 -359
  67. package/src/utils/canvas/elementStyles.ts +42 -1
@@ -13,6 +13,7 @@ import {
13
13
  normalizeTextType
14
14
  } from '../utils/textTypes'
15
15
  import { applyGroupGeometryFlip } from '../utils/groupFlip'
16
+ import { getFontVariants, getFontVariantStyle } from '../configs/fonts'
16
17
  import type { CustomUploadHandler, UploadResourceContext, UploadResourceType } from '../types'
17
18
 
18
19
  export type { SlideTextType }
@@ -65,6 +66,8 @@ export interface SlideElement {
65
66
  link?: string
66
67
  locked?: boolean
67
68
  isMaster?: boolean
69
+ isPlaceholder?: boolean
70
+ placeholder?: any
68
71
  hasCustomZIndex?: boolean
69
72
  groupId?: string
70
73
  elements?: SlideElement[]
@@ -266,6 +269,10 @@ export interface Slide {
266
269
  notes?: string
267
270
  animations?: PPTAnimation[]
268
271
  transition?: string
272
+ transitionDuration?: number
273
+ autoAdvance?: boolean
274
+ autoAdvanceDelay?: number
275
+ clickAdvance?: boolean
269
276
  schema?: any
270
277
  }
271
278
 
@@ -822,18 +829,17 @@ export function createDefaultSlideMasters(W: number = 1280, H: number = 720): Sl
822
829
  type: 'shape',
823
830
  name: '图片占位符',
824
831
  content: 'rect',
825
- text: '<div>图片占位符</div>',
832
+ text: '',
833
+ isPlaceholder: true,
834
+ placeholder: { type: 'image', prompt: '图片占位符' },
826
835
  style: {
827
836
  left: `${marginX}px`,
828
837
  top: '80px',
829
838
  width: `${colW}px`,
830
839
  height: `${Math.max(200, H - 80 - 55)}px`,
831
- fill: '#f8fafc',
832
- borderColor: '#cbd5e1',
833
- borderWidth: '1px',
834
- borderStyle: 'dashed',
835
- borderRadius: '4px',
836
- textColor: '#94a3b8',
840
+ fill: '#d9dde3',
841
+ borderRadius: '12px',
842
+ textColor: '#475569',
837
843
  fontSize: '16px',
838
844
  textAlign: 'center'
839
845
  }
@@ -1024,20 +1030,45 @@ function applyBackgroundToAll() {
1024
1030
  if (!activeSlide.value) return
1025
1031
  const bg = activeSlide.value.background
1026
1032
  const type = activeSlide.value.bgType || 'color'
1033
+ const imgMode = (activeSlide.value as any).bgImageMode
1034
+ const isPattern = (activeSlide.value as any).isPattern
1035
+ const bgPatternType = (activeSlide.value as any).bgPatternType
1036
+ const bgPatternFg = (activeSlide.value as any).bgPatternFg
1037
+ const bgPatternBg = (activeSlide.value as any).bgPatternBg
1038
+ const lastImgUrl = (activeSlide.value as any)._lastBgImageUrl
1027
1039
  slides.value.forEach(slide => {
1028
1040
  slide.background = bg
1029
1041
  slide.bgType = type
1042
+ if (imgMode !== undefined) (slide as any).bgImageMode = imgMode
1043
+ if (isPattern !== undefined) (slide as any).isPattern = isPattern
1044
+ if (bgPatternType !== undefined) (slide as any).bgPatternType = bgPatternType
1045
+ if (bgPatternFg !== undefined) (slide as any).bgPatternFg = bgPatternFg
1046
+ if (bgPatternBg !== undefined) (slide as any).bgPatternBg = bgPatternBg
1047
+ if (lastImgUrl !== undefined) (slide as any)._lastBgImageUrl = lastImgUrl
1030
1048
  })
1031
1049
  if (isMasterEditingView.value || slideMasters.value.length > 0) {
1032
1050
  slideMasters.value.forEach(m => {
1033
1051
  m.background = bg
1034
1052
  m.bgType = type
1053
+ if (imgMode !== undefined) (m as any).bgImageMode = imgMode
1054
+ if (isPattern !== undefined) (m as any).isPattern = isPattern
1055
+ if (bgPatternType !== undefined) (m as any).bgPatternType = bgPatternType
1056
+ if (bgPatternFg !== undefined) (m as any).bgPatternFg = bgPatternFg
1057
+ if (bgPatternBg !== undefined) (m as any).bgPatternBg = bgPatternBg
1058
+ if (lastImgUrl !== undefined) (m as any)._lastBgImageUrl = lastImgUrl
1035
1059
  m.layouts?.forEach(l => {
1036
1060
  l.background = bg
1037
1061
  l.bgType = type
1062
+ if (imgMode !== undefined) (l as any).bgImageMode = imgMode
1063
+ if (isPattern !== undefined) (l as any).isPattern = isPattern
1064
+ if (bgPatternType !== undefined) (l as any).bgPatternType = bgPatternType
1065
+ if (bgPatternFg !== undefined) (l as any).bgPatternFg = bgPatternFg
1066
+ if (bgPatternBg !== undefined) (l as any).bgPatternBg = bgPatternBg
1067
+ if (lastImgUrl !== undefined) (l as any)._lastBgImageUrl = lastImgUrl
1038
1068
  })
1039
1069
  })
1040
1070
  }
1071
+ addHistorySnapshot()
1041
1072
  notify('已应用背景到全部幻灯片')
1042
1073
  }
1043
1074
 
@@ -1099,13 +1130,139 @@ function applyThemeToAll() {
1099
1130
  }
1100
1131
 
1101
1132
  function applyFontToAll() {
1102
- slides.value.forEach(slide => {
1103
- slide.elements.forEach(el => {
1104
- if (el.type === 'text') {
1105
- el.style.fontFamily = globalFontFamily.value
1133
+ const rawFont = globalFontFamily.value
1134
+ const hasExplicitFont = Boolean(rawFont && rawFont !== '默认字体')
1135
+ const targetColor = globalFontColor.value
1136
+
1137
+ const updateHtmlFontAndColor = (htmlStr: string, effectiveFont: string) => {
1138
+ if (!htmlStr || typeof htmlStr !== 'string') return htmlStr
1139
+ let res = htmlStr
1140
+ if (/font-family\s*:/i.test(res)) {
1141
+ res = res.replace(/font-family\s*:\s*[^;'"\}]+;?/gi, `font-family: ${effectiveFont};`)
1142
+ }
1143
+ if (/<font\b[^>]*\bface=/i.test(res)) {
1144
+ res = res.replace(/(<font\b[^>]*?\bface\s*=\s*['"])([^'"]+)(['"])/gi, `$1${effectiveFont}$3`)
1145
+ }
1146
+ if (targetColor) {
1147
+ if (/(?:^|[;"'\s\{])color\s*:/i.test(res)) {
1148
+ res = res.replace(/(^|[;"'\s\{])(color\s*:\s*)([^;'"\}]+)/gi, `$1$2${targetColor}`)
1149
+ }
1150
+ if (/<font\b[^>]*\bcolor=/i.test(res)) {
1151
+ res = res.replace(/(<font\b[^>]*?\bcolor\s*=\s*['"])([^'"]+)(['"])/gi, `$1${targetColor}$3`)
1152
+ }
1153
+ }
1154
+ return res
1155
+ }
1156
+
1157
+ const applyToElementList = (elements: any[], slideRecipe: any) => {
1158
+ if (!Array.isArray(elements)) return
1159
+ elements.forEach((el: any) => {
1160
+ if (!el || typeof el !== 'object') return
1161
+ if (el.type === 'group' && Array.isArray(el.elements)) {
1162
+ applyToElementList(el.elements, slideRecipe)
1163
+ return
1164
+ }
1165
+ if (el.type === 'text' || el.type === 'shape') {
1166
+ if (!el.style) el.style = {}
1167
+ const isHeading = el.textType === 'title' || el.textType === 'headline' || (el.id && String(el.id).startsWith('title'))
1168
+ const fallbackFont = isHeading
1169
+ ? (slideRecipe?.styleFonts?.titleFont || 'SourceHanSansCN')
1170
+ : (slideRecipe?.styleFonts?.bodyFont || 'SourceHanSansCN')
1171
+ const effectiveFont = hasExplicitFont ? rawFont : fallbackFont
1172
+
1173
+ el.style.fontFamily = hasExplicitFont ? rawFont : effectiveFont
1174
+ el.fontFamily = hasExplicitFont ? rawFont : effectiveFont
1175
+
1176
+ const variants = getFontVariants(effectiveFont)
1177
+ const firstVariant = variants[0]?.value || 'Regular'
1178
+ el.style.fontFamilyStyle = firstVariant
1179
+ const vStyle = getFontVariantStyle(effectiveFont, firstVariant)
1180
+ if (vStyle) {
1181
+ el.style.fontWeight = vStyle.fontWeight
1182
+ el.style.fontStyle = vStyle.fontStyle
1183
+ if (vStyle.faceName) {
1184
+ ;(el.style as any).fontVariantFace = vStyle.faceName
1185
+ } else {
1186
+ delete (el.style as any).fontVariantFace
1187
+ }
1188
+ }
1189
+
1190
+ if (targetColor) {
1191
+ el.style.color = targetColor
1192
+ el.style.textColor = targetColor
1193
+ el.color = targetColor
1194
+ el.textColor = targetColor
1195
+ if (el.text && typeof el.text === 'object') {
1196
+ el.text.defaultColor = targetColor
1197
+ }
1198
+ }
1199
+
1200
+ if (el.content && typeof el.content === 'string') {
1201
+ el.content = updateHtmlFontAndColor(el.content, effectiveFont)
1202
+ }
1203
+ if (el.text) {
1204
+ if (typeof el.text === 'string') {
1205
+ el.text = updateHtmlFontAndColor(el.text, effectiveFont)
1206
+ } else if (typeof el.text.content === 'string') {
1207
+ el.text.content = updateHtmlFontAndColor(el.text.content, effectiveFont)
1208
+ }
1209
+ }
1210
+
1211
+ if (typeof document !== 'undefined') {
1212
+ const container = document.getElementById(`editable-${el.id}`)
1213
+ if (container) {
1214
+ container.style.fontFamily = effectiveFont
1215
+ if (targetColor) {
1216
+ container.style.color = targetColor
1217
+ }
1218
+ container.querySelectorAll('*').forEach((child: any) => {
1219
+ if (child.style) {
1220
+ if (child.style.fontFamily) {
1221
+ child.style.fontFamily = effectiveFont
1222
+ }
1223
+ if (targetColor && child.style.color) {
1224
+ child.style.color = targetColor
1225
+ }
1226
+ }
1227
+ if (child.tagName === 'FONT') {
1228
+ child.setAttribute('face', effectiveFont)
1229
+ child.style.fontFamily = effectiveFont
1230
+ if (targetColor) {
1231
+ child.setAttribute('color', targetColor)
1232
+ child.style.color = targetColor
1233
+ }
1234
+ }
1235
+ })
1236
+ if (!el.content || !el.content.includes('<table')) {
1237
+ if (el.type === 'shape') {
1238
+ if (typeof el.text === 'string') {
1239
+ el.text = container.innerHTML
1240
+ } else if (el.text && typeof el.text === 'object') {
1241
+ el.text.content = container.innerHTML
1242
+ }
1243
+ } else {
1244
+ el.content = container.innerHTML
1245
+ }
1246
+ }
1247
+ }
1248
+ }
1106
1249
  }
1107
1250
  })
1251
+ }
1252
+
1253
+ slides.value.forEach(slide => {
1254
+ const slideRecipe = getActiveSlideThemeRecipe(slide)
1255
+ applyToElementList(slide.elements, slideRecipe)
1108
1256
  })
1257
+
1258
+ if (isMasterEditingView.value && activeSlide.value?.elements) {
1259
+ const masterRecipe = getActiveSlideThemeRecipe(activeSlide.value)
1260
+ applyToElementList(activeSlide.value.elements, masterRecipe)
1261
+ }
1262
+
1263
+ markAllPagesDirty()
1264
+ addHistorySnapshot()
1265
+ activeSlideVersion.value++
1109
1266
  notify('已统一全局字体')
1110
1267
  }
1111
1268
 
@@ -1646,6 +1803,24 @@ function resolveSlideMasterAndLayout(slide?: any): { masterId: string, layoutId:
1646
1803
  }
1647
1804
  }
1648
1805
 
1806
+ function formatBareUrlWithMode(bg: any, mode?: string, isPattern?: boolean): any {
1807
+ if (typeof bg !== 'string') return bg
1808
+ const trimmed = bg.trim()
1809
+ if (!trimmed.startsWith('url(')) return bg
1810
+ const closeParenIdx = trimmed.lastIndexOf(')')
1811
+ if (closeParenIdx === -1) return bg
1812
+ const afterUrl = trimmed.slice(closeParenIdx + 1).trim()
1813
+ if (afterUrl) return bg
1814
+ const urlPart = trimmed.slice(0, closeParenIdx + 1)
1815
+ if (trimmed.includes('data:image/svg+xml') || isPattern) {
1816
+ return `${urlPart} left top / auto repeat`
1817
+ }
1818
+ if (mode === 'contain') return `${urlPart} center center / contain no-repeat`
1819
+ if (mode === 'cover') return `${urlPart} center center / 60% auto no-repeat`
1820
+ if (mode === 'tile') return `${urlPart} left top / 25% auto repeat`
1821
+ return `${urlPart} center center / 100% 100% no-repeat`
1822
+ }
1823
+
1649
1824
  export function getEffectiveSlideBackground(slide?: any): any {
1650
1825
  if (!slide) return '#ffffff'
1651
1826
 
@@ -1653,16 +1828,30 @@ export function getEffectiveSlideBackground(slide?: any): any {
1653
1828
  if (isMasterEditingView.value) {
1654
1829
  const master = slideMasters.value.find(m => m.id === activeMasterId.value) || slideMasters.value[0]
1655
1830
  const layout = activeLayoutId.value ? master?.layouts?.find(l => l.id === activeLayoutId.value) : null
1656
- return layout?.background || master?.background || '#ffffff'
1831
+ if (layout?.background && layout.background !== '#ffffff' && layout.background !== 'none') {
1832
+ return formatBareUrlWithMode(layout.background, (layout as any).bgImageMode, (layout as any).isPattern)
1833
+ }
1834
+ if (master?.background) {
1835
+ return formatBareUrlWithMode(master.background, (master as any).bgImageMode, (master as any).isPattern)
1836
+ }
1837
+ return '#ffffff'
1657
1838
  }
1658
1839
 
1659
1840
  // 2. 如果当前幻灯片自身设置了明确的独立自定义背景(包含 url、gradient 或非白色自定义色)
1660
- const directBg = slide.schema?.bgColor || slide.background || slide.fill
1841
+ const hasSlideBgSpecial = typeof slide.background === 'string' && (
1842
+ slide.background.includes('url(') ||
1843
+ slide.background.includes('gradient(') ||
1844
+ slide.bgType === 'image' ||
1845
+ slide.bgType === 'gradient'
1846
+ )
1847
+ const directBg = hasSlideBgSpecial
1848
+ ? (slide.background || slide.fill || slide.schema?.bgColor)
1849
+ : (slide.schema?.bgColor || slide.background || slide.fill)
1661
1850
  if (directBg) {
1662
1851
  if (typeof directBg === 'string') {
1663
1852
  const trimmed = directBg.trim().toLowerCase()
1664
1853
  if (trimmed.includes('url(') || trimmed.includes('gradient(') || (!['#ffffff', '#fff', 'white', 'transparent', 'none', ''].includes(trimmed))) {
1665
- return directBg
1854
+ return formatBareUrlWithMode(directBg, slide.bgImageMode, slide.isPattern)
1666
1855
  }
1667
1856
  } else if (typeof directBg === 'object' && (directBg.image || directBg.gradient || (directBg.color && !['#ffffff', '#fff', 'white'].includes(String(directBg.color).toLowerCase())))) {
1668
1857
  return directBg
@@ -1675,13 +1864,13 @@ export function getEffectiveSlideBackground(slide?: any): any {
1675
1864
  const layout = layoutId && master ? master.layouts?.find(l => l && l.id === layoutId) : null
1676
1865
 
1677
1866
  if (layout?.background && layout.background !== '#ffffff' && layout.background !== 'none') {
1678
- return layout.background
1867
+ return formatBareUrlWithMode(layout.background, (layout as any).bgImageMode, (layout as any).isPattern)
1679
1868
  }
1680
1869
  if (master?.background && master.background !== '#ffffff' && master.background !== 'none') {
1681
- return master.background
1870
+ return formatBareUrlWithMode(master.background, (master as any).bgImageMode, (master as any).isPattern)
1682
1871
  }
1683
1872
 
1684
- return directBg || '#ffffff'
1873
+ return directBg ? formatBareUrlWithMode(directBg, slide.bgImageMode, slide.isPattern) : '#ffffff'
1685
1874
  }
1686
1875
 
1687
1876
  function applyStyleRecipeToMaster(
@@ -2033,8 +2222,8 @@ function getThemeUsageSlides(themeId: string): number[] {
2033
2222
  const pages: number[] = []
2034
2223
  const hasAnyExplicitTheme = slides.value.some(s => !!s.themeId)
2035
2224
  slides.value.forEach((slide, idx) => {
2036
- const effId = slide.themeId || (hasAnyExplicitTheme ? 'default-recipe' : 'default-recipe')
2037
- if (effId === themeId) {
2225
+ const effId = slide.themeId || (hasAnyExplicitTheme ? 'tech-blue' : 'tech-blue')
2226
+ if (effId === themeId || (themeId === 'tech-blue' && (effId === 'default-recipe' || !effId))) {
2038
2227
  pages.push(idx + 1)
2039
2228
  }
2040
2229
  })
@@ -2062,8 +2251,8 @@ function getActiveSlideThemeRecipe(targetSlide?: any): StyleRecipe {
2062
2251
  } else if (activeSlide.value) {
2063
2252
  effSlide = activeSlide.value
2064
2253
  }
2065
- const themeId = effSlide?.themeId || (isMasterEditingView.value ? ((slideMasters.value.find(m => m.id === activeMasterId.value) as any)?.themeId || 'default-recipe') : 'default-recipe')
2066
- const recipe = builtinStyleRecipes.find(r => r.id === themeId) || defaultStyleRecipe
2254
+ const themeId = effSlide?.themeId || (isMasterEditingView.value ? ((slideMasters.value.find(m => m.id === activeMasterId.value) as any)?.themeId || 'tech-blue') : 'tech-blue')
2255
+ const recipe = builtinStyleRecipes.find(r => r.id === themeId || (themeId === 'default-recipe' && r.id === 'tech-blue')) || defaultStyleRecipe
2067
2256
  return recipe
2068
2257
  }
2069
2258
 
@@ -2159,13 +2348,22 @@ const activeSlide = computed<Slide>({
2159
2348
  if (prop === 'id') return target.id
2160
2349
  if (prop === 'title') return target.name || '母版编辑'
2161
2350
  if (prop === 'subtitle') return ''
2162
- if (prop === 'background') return target.background || master?.background || '#ffffff'
2351
+ if (prop === 'background') return target.background !== undefined ? target.background : (master?.background || '#ffffff')
2163
2352
  if (prop === 'bgType') {
2164
2353
  if (target.bgType) return target.bgType
2165
2354
  if (master?.bgType) return master.bgType
2166
2355
  const effBg = target.background || master?.background
2167
2356
  return normalizeSlideBackground(effBg).bgType || 'color'
2168
2357
  }
2358
+ if (prop === 'bgImageMode') {
2359
+ return (target as any).bgImageMode || (master as any)?.bgImageMode || 'stretch'
2360
+ }
2361
+ if (prop === 'isPattern') {
2362
+ return (target as any).isPattern ?? (master as any)?.isPattern ?? false
2363
+ }
2364
+ if (prop === '_lastBgImageUrl') {
2365
+ return (target as any)._lastBgImageUrl !== undefined ? (target as any)._lastBgImageUrl : ((master as any)?._lastBgImageUrl || '')
2366
+ }
2169
2367
  if (prop === 'themeId') return target.themeId || (master as any)?.themeId || 'default-recipe'
2170
2368
  if (prop === 'themeName') return target.themeName || (master as any)?.themeName || ''
2171
2369
  if (prop === 'themeColors') return (master as any)?.themeColors || globalThemeColor.value
@@ -2173,10 +2371,16 @@ const activeSlide = computed<Slide>({
2173
2371
  if (!target.elements) target.elements = []
2174
2372
  // 确保版式或母版中的占位符存在于 target.elements 中,使用户在母版视图下可以直接点击选中、拖拽调整与编辑
2175
2373
  if (target.placeholders && Array.isArray(target.placeholders)) {
2176
- for (const ph of target.placeholders) {
2177
- if (!target.elements.some((el: any) => el.id === ph.id || (Math.abs((el.left || 0) - (ph.left || 0)) < 5 && Math.abs((el.top || 0) - (ph.top || 0)) < 5))) {
2178
- target.elements.push(ph)
2374
+ if (target.type === 'custom' && target.elements.length > 0) {
2375
+ target.placeholders = target.elements.filter((x: any) => x && (x.isPlaceholder || x.placeholder))
2376
+ target._placeholdersMerged = true
2377
+ } else if (!target._placeholdersMerged) {
2378
+ for (const ph of target.placeholders) {
2379
+ if (!target.elements.some((el: any) => el.id === ph.id || (Math.abs((el.left || 0) - (ph.left || 0)) < 5 && Math.abs((el.top || 0) - (ph.top || 0)) < 5))) {
2380
+ target.elements.push(ph)
2381
+ }
2179
2382
  }
2383
+ target._placeholdersMerged = true
2180
2384
  }
2181
2385
  }
2182
2386
  // 🌟 兜底保障:若当前选中的是顶级母版(target === master),且母版本身 elements 为空且无 placeholders(如自定义极简母版):
@@ -2220,27 +2424,39 @@ const activeSlide = computed<Slide>({
2220
2424
  if (prop === 'elements') {
2221
2425
  target.elements = value
2222
2426
  if (Array.isArray(value)) {
2223
- const phs = value.filter((x: any) => x && (x.isPlaceholder || x.placeholder))
2224
- if (phs.length > 0) {
2225
- target.placeholders = phs
2226
- }
2427
+ target.placeholders = value.filter((x: any) => x && (x.isPlaceholder || x.placeholder))
2227
2428
  }
2228
2429
  return true
2229
2430
  }
2230
2431
  if (prop === 'background') {
2432
+ const prevBg = target.background
2231
2433
  target.background = value
2232
2434
  if (target === master && master.layouts) {
2233
2435
  master.layouts.forEach((l: any) => {
2234
- if (!l.background || l.background === '#ffffff') l.background = value
2436
+ if (!l.background || l.background === '#ffffff' || l.background === prevBg) {
2437
+ l.background = value
2438
+ }
2235
2439
  })
2236
2440
  }
2237
2441
  return true
2238
2442
  }
2239
2443
  if (prop === 'bgType') {
2444
+ const prevType = target.bgType
2240
2445
  target.bgType = value
2241
2446
  if (target === master && master.layouts) {
2242
2447
  master.layouts.forEach((l: any) => {
2243
- if (!l.bgType || l.bgType === 'color') l.bgType = value
2448
+ if (!l.bgType || l.bgType === 'color' || l.bgType === prevType) {
2449
+ l.bgType = value
2450
+ }
2451
+ })
2452
+ }
2453
+ return true
2454
+ }
2455
+ if (prop === 'bgImageMode' || prop === 'isPattern' || prop === '_lastBgImageUrl') {
2456
+ target[prop] = value
2457
+ if (target === master && master.layouts) {
2458
+ master.layouts.forEach((l: any) => {
2459
+ l[prop] = value
2244
2460
  })
2245
2461
  }
2246
2462
  return true
@@ -2362,11 +2578,23 @@ const formatedAnimations = computed(() => {
2362
2578
  return formatted
2363
2579
  })
2364
2580
 
2581
+ function findElementInSlideElements(id: string, elements: any[] = activeSlide.value?.elements || []): any {
2582
+ if (!id || !elements || !elements.length) return null
2583
+ for (const el of elements) {
2584
+ if (el.id === id) return el
2585
+ if (el.type === 'group' && Array.isArray(el.elements)) {
2586
+ const found = findElementInSlideElements(id, el.elements)
2587
+ if (found) return found
2588
+ }
2589
+ }
2590
+ return null
2591
+ }
2592
+
2365
2593
  function addAnimation(type: 'in' | 'out' | 'attention', effect: string) {
2366
2594
  if (!activeSlide.value || !selectedElementId.value) return
2367
2595
  if (!activeSlide.value.animations) activeSlide.value.animations = []
2368
2596
 
2369
- const targetEl = activeSlide.value.elements.find(e => e.id === selectedElementId.value)
2597
+ const targetEl = findElementInSlideElements(selectedElementId.value, activeSlide.value.elements)
2370
2598
  if (!targetEl) return
2371
2599
 
2372
2600
  const groupId = targetEl.groupId
@@ -2400,7 +2628,7 @@ function deleteAnimation(id: string) {
2400
2628
  const targetAnim = activeSlide.value.animations.find(a => a.id === id)
2401
2629
  if (!targetAnim) return
2402
2630
 
2403
- const targetEl = activeSlide.value.elements.find(e => e.id === targetAnim.elId)
2631
+ const targetEl = findElementInSlideElements(targetAnim.elId, activeSlide.value.elements)
2404
2632
  const groupId = targetEl?.groupId
2405
2633
 
2406
2634
  if (groupId) {
@@ -2419,7 +2647,7 @@ function updateAnimationDuration(id: string, duration: number) {
2419
2647
  const targetAnim = activeSlide.value.animations.find(a => a.id === id)
2420
2648
  if (!targetAnim) return
2421
2649
 
2422
- const targetEl = activeSlide.value.elements.find(e => e.id === targetAnim.elId)
2650
+ const targetEl = findElementInSlideElements(targetAnim.elId, activeSlide.value.elements)
2423
2651
  const groupId = targetEl?.groupId
2424
2652
 
2425
2653
  if (groupId) {
@@ -2447,7 +2675,7 @@ function updateAnimationEffect(id: string, type: 'in' | 'out' | 'attention', eff
2447
2675
  const targetAnim = activeSlide.value.animations.find(a => a.id === id)
2448
2676
  if (!targetAnim) return
2449
2677
 
2450
- const targetEl = activeSlide.value.elements.find(e => e.id === targetAnim.elId)
2678
+ const targetEl = findElementInSlideElements(targetAnim.elId, activeSlide.value.elements)
2451
2679
  const groupId = targetEl?.groupId
2452
2680
 
2453
2681
  if (groupId) {
@@ -2463,6 +2691,15 @@ function updateAnimationEffect(id: string, type: 'in' | 'out' | 'attention', eff
2463
2691
  }
2464
2692
  }
2465
2693
 
2694
+ function updateAnimationTargetElement(id: string, newElId: string) {
2695
+ if (!activeSlide.value || !activeSlide.value.animations || !newElId) return
2696
+ const targetAnim = activeSlide.value.animations.find(a => a.id === id)
2697
+ if (!targetAnim) return
2698
+ targetAnim.elId = newElId
2699
+ addHistorySnapshot()
2700
+ notify('已将动画修改到对应元素')
2701
+ }
2702
+
2466
2703
  function updateAnimationOrder(oldIndex: number, newIndex: number) {
2467
2704
  if (!activeSlide.value || !activeSlide.value.animations) return
2468
2705
  if (oldIndex === undefined || newIndex === undefined || oldIndex === newIndex) return
@@ -4030,8 +4267,10 @@ function addElement(
4030
4267
  const typeConf = TEXT_TYPE_CONFIGS[textType] || TEXT_TYPE_CONFIGS.normalText
4031
4268
  const textCfg = recipe.textStyles?.[textType] || defaultStyleRecipe.textStyles[textType] || defaultStyleRecipe.textStyles.normalText
4032
4269
  const isHeading = textType === 'title' || textType === 'headline'
4033
- const textFont = textCfg.fontFamily || (isHeading ? (recipe.styleFonts?.titleFont || 'SourceHanSansCN') : (recipe.styleFonts?.bodyFont || 'SourceHanSansCN'))
4034
- const textColor = textCfg.color || (textType === 'smallText' ? '#64748b' : (isHeading ? (recipe.textStyles?.title?.color || '#0f172a') : (recipe.textStyles?.normalText?.color || '#334155')))
4270
+ const textFont = (globalFontFamily.value && globalFontFamily.value !== '默认字体')
4271
+ ? globalFontFamily.value
4272
+ : (textCfg.fontFamily || (isHeading ? (recipe.styleFonts?.titleFont || 'SourceHanSansCN') : (recipe.styleFonts?.bodyFont || 'SourceHanSansCN')))
4273
+ const textColor = globalFontColor.value || textCfg.color || (textType === 'smallText' ? '#64748b' : (isHeading ? (recipe.textStyles?.title?.color || '#0f172a') : (recipe.textStyles?.normalText?.color || '#334155')))
4035
4274
  const fontSize = `${textCfg.fontSize || typeConf.defaultFontSize}px`
4036
4275
  const fontWeight = textCfg.fontWeight || typeConf.defaultFontWeight
4037
4276
  const lineHeight = textCfg.lineHeight || typeConf.defaultLineHeight
@@ -4062,9 +4301,11 @@ function addElement(
4062
4301
  const shapeFill = recipe.shape?.fillColor || (recipe.styleColors && recipe.styleColors[0]) || '#2563eb'
4063
4302
  const shapeBorder = recipe.shape?.borderColor || (recipe.styleColors && recipe.styleColors[1]) || '#1d4ed8'
4064
4303
  const shapeTextCol = recipe.shape?.textColor || '#ffffff'
4065
- const shapeFont = (recipe.styleFonts?.bodyFont && recipe.styleFonts.bodyFont !== '默认字体')
4066
- ? recipe.styleFonts.bodyFont
4067
- : 'SourceHanSansCN'
4304
+ const shapeFont = (globalFontFamily.value && globalFontFamily.value !== '默认字体')
4305
+ ? globalFontFamily.value
4306
+ : ((recipe.styleFonts?.bodyFont && recipe.styleFonts.bodyFont !== '默认字体')
4307
+ ? recipe.styleFonts.bodyFont
4308
+ : 'SourceHanSansCN')
4068
4309
 
4069
4310
  style = {
4070
4311
  top: '40%',
@@ -4274,10 +4515,10 @@ function addElementAt(
4274
4515
  let style: any = {}
4275
4516
 
4276
4517
  if (type === 'text') {
4277
- const textColor = recipe.textStyles?.title?.color || recipe.styleColors?.[0] || globalFontColor.value || recipe.textStyles?.normalText?.color || '#0f172a'
4278
- const textFont = (recipe.styleFonts?.bodyFont && recipe.styleFonts.bodyFont !== '默认字体')
4279
- ? recipe.styleFonts.bodyFont
4280
- : (globalFontFamily.value && globalFontFamily.value !== '默认字体' ? globalFontFamily.value : 'SourceHanSansCN')
4518
+ const textColor = globalFontColor.value || recipe.textStyles?.title?.color || recipe.styleColors?.[0] || recipe.textStyles?.normalText?.color || '#0f172a'
4519
+ const textFont = (globalFontFamily.value && globalFontFamily.value !== '默认字体')
4520
+ ? globalFontFamily.value
4521
+ : ((recipe.styleFonts?.bodyFont && recipe.styleFonts.bodyFont !== '默认字体') ? recipe.styleFonts.bodyFont : 'SourceHanSansCN')
4281
4522
  content = `<div style="color:${textColor}; font-family:${textFont};"><br></div>`
4282
4523
  style = {
4283
4524
  top: `${topPct.toFixed(2)}%`,
@@ -4306,9 +4547,11 @@ function addElementAt(
4306
4547
  const shapeFill = recipe.shape?.fillColor || (recipe.styleColors && recipe.styleColors[0]) || '#2563eb'
4307
4548
  const shapeBorder = recipe.shape?.borderColor || (recipe.styleColors && recipe.styleColors[1]) || '#1d4ed8'
4308
4549
  const shapeTextCol = recipe.shape?.textColor || '#ffffff'
4309
- const shapeFont = (recipe.styleFonts?.bodyFont && recipe.styleFonts.bodyFont !== '默认字体')
4310
- ? recipe.styleFonts.bodyFont
4311
- : 'SourceHanSansCN'
4550
+ const shapeFont = (globalFontFamily.value && globalFontFamily.value !== '默认字体')
4551
+ ? globalFontFamily.value
4552
+ : ((recipe.styleFonts?.bodyFont && recipe.styleFonts.bodyFont !== '默认字体')
4553
+ ? recipe.styleFonts.bodyFont
4554
+ : 'SourceHanSansCN')
4312
4555
 
4313
4556
  style = {
4314
4557
  top: `${topPct.toFixed(2)}%`,
@@ -4354,7 +4597,9 @@ function addElementAt(
4354
4597
  content = ''
4355
4598
  const chartPalette = (recipe.chart?.palette && recipe.chart.palette.length > 0) ? [...recipe.chart.palette] : (recipe.styleColors || ['#2563eb', '#3b82f6', '#60a5fa'])
4356
4599
  const chartTextCol = recipe.chart?.textColor || recipe.chart?.labelColor || '#1e293b'
4357
- const chartFont = recipe.chart?.fontFamily || recipe.styleFonts?.bodyFont || 'SourceHanSansCN'
4600
+ const chartFont = (globalFontFamily.value && globalFontFamily.value !== '默认字体')
4601
+ ? globalFontFamily.value
4602
+ : (recipe.chart?.fontFamily || recipe.styleFonts?.bodyFont || 'SourceHanSansCN')
4358
4603
 
4359
4604
  style = {
4360
4605
  top: `${topPct.toFixed(2)}%`,
@@ -4379,6 +4624,9 @@ function addElementAt(
4379
4624
 
4380
4625
  if (type === 'text') {
4381
4626
  newElement.verticalAlign = 'middle'
4627
+ newElement.color = style.color
4628
+ newElement.textColor = style.color
4629
+ newElement.fontFamily = style.fontFamily
4382
4630
  }
4383
4631
 
4384
4632
  if (type === 'shape') {
@@ -8847,16 +9095,18 @@ export function useSlides() {
8847
9095
  function addMaster(name = '自定义母版') {
8848
9096
  const newMasterId = 'master-' + Date.now().toString()
8849
9097
  const newLayoutId = 'layout-' + Date.now().toString()
8850
- const dateStr = getCurrentFormattedDate()
8851
9098
  const W = logicalWidth.value || 1280
8852
9099
  const H = logicalHeight.value || 720
8853
9100
 
8854
- const masterElements: SlideElement[] = [
9101
+ const createPlaceholders = (scope: 'master' | 'layout') => [
8855
9102
  {
8856
- id: 'ph-title-' + Date.now().toString(),
8857
- type: 'text',
9103
+ id: `ph-title-${scope}-${Date.now().toString(36)}`,
9104
+ type: 'text' as const,
8858
9105
  name: '母版标题占位符',
8859
9106
  content: '<div>单击此处编辑母版标题样式</div>',
9107
+ isMaster: true,
9108
+ isPlaceholder: true,
9109
+ placeholder: { type: 'title', prompt: '单击此处编辑母版标题样式' },
8860
9110
  style: {
8861
9111
  left: '50px',
8862
9112
  top: '40px',
@@ -8868,10 +9118,13 @@ export function useSlides() {
8868
9118
  }
8869
9119
  },
8870
9120
  {
8871
- id: 'ph-body-' + Date.now().toString(),
8872
- type: 'text',
9121
+ id: `ph-body-${scope}-${Date.now().toString(36)}`,
9122
+ type: 'text' as const,
8873
9123
  name: '母版文本占位符',
8874
9124
  content: '<div>• 单击此处编辑母版文本样式</div><div style="padding-left: 24px;">• 第二级</div><div style="padding-left: 48px;">• 第三级</div><div style="padding-left: 72px;">• 第四级</div><div style="padding-left: 96px;">• 第五级</div>',
9125
+ isMaster: true,
9126
+ isPlaceholder: true,
9127
+ placeholder: { type: 'body', prompt: '单击此处编辑母版文本样式' },
8875
9128
  style: {
8876
9129
  left: '50px',
8877
9130
  top: '130px',
@@ -8881,51 +9134,6 @@ export function useSlides() {
8881
9134
  color: '#475569',
8882
9135
  lineHeight: '1.6'
8883
9136
  }
8884
- },
8885
- {
8886
- id: 'ph-date-' + Date.now().toString(),
8887
- type: 'text',
8888
- name: '日期占位符',
8889
- content: `<div>${dateStr}</div>`,
8890
- style: {
8891
- left: '50px',
8892
- top: `${H - 48}px`,
8893
- width: '220px',
8894
- height: '30px',
8895
- fontSize: '12px',
8896
- color: '#94a3b8',
8897
- textAlign: 'left'
8898
- }
8899
- },
8900
- {
8901
- id: 'ph-footer-' + Date.now().toString(),
8902
- type: 'text',
8903
- name: '页脚占位符',
8904
- content: '<div>页脚区</div>',
8905
- style: {
8906
- left: `${Math.round(W / 2 - 110)}px`,
8907
- top: `${H - 48}px`,
8908
- width: '220px',
8909
- height: '30px',
8910
- fontSize: '12px',
8911
- color: '#94a3b8',
8912
- textAlign: 'center'
8913
- }
8914
- },
8915
- {
8916
- id: 'ph-num-' + Date.now().toString(),
8917
- type: 'text',
8918
- name: '页码占位符',
8919
- content: '<div>&lt;#&gt;</div>',
8920
- style: {
8921
- left: `${W - 270}px`,
8922
- top: `${H - 48}px`,
8923
- width: '220px',
8924
- height: '30px',
8925
- fontSize: '12px',
8926
- color: '#94a3b8',
8927
- textAlign: 'right'
8928
- }
8929
9137
  }
8930
9138
  ]
8931
9139
 
@@ -8935,13 +9143,14 @@ export function useSlides() {
8935
9143
  background: '#ffffff',
8936
9144
  themeColors: ['#3b82f6', '#f59e0b', '#ef4444', '#10b981', '#8b5cf6'],
8937
9145
  fontFamily: 'SourceHanSansCN',
8938
- elements: masterElements,
9146
+ elements: [],
9147
+ placeholders: createPlaceholders('master'),
8939
9148
  layouts: [
8940
9149
  {
8941
9150
  id: newLayoutId,
8942
9151
  name: '默认版式',
8943
9152
  type: 'title-body',
8944
- elements: masterElements,
9153
+ elements: createPlaceholders('layout'),
8945
9154
  placeholders: []
8946
9155
  }
8947
9156
  ]
@@ -8956,16 +9165,17 @@ export function useSlides() {
8956
9165
  const targetMaster = slideMasters.value.find(m => m.id === masterId)
8957
9166
  if (!targetMaster) return
8958
9167
  const newLayoutId = 'layout-' + Date.now().toString() + Math.random().toString(36).substring(2, 6)
8959
- const dateStr = getCurrentFormattedDate()
8960
9168
  const W = logicalWidth.value || 1280
8961
- const H = logicalHeight.value || 720
8962
9169
 
8963
9170
  const layoutElements: SlideElement[] = [
8964
9171
  {
8965
- id: 'ph-title-' + Date.now().toString(),
9172
+ id: 'ph-title-' + Date.now().toString(36) + Math.random().toString(36).substring(2, 6),
8966
9173
  type: 'text',
8967
9174
  name: '母版标题占位符',
8968
9175
  content: '<div>单击此处编辑母版标题样式</div>',
9176
+ isMaster: true,
9177
+ isPlaceholder: true,
9178
+ placeholder: { type: 'title', prompt: '单击此处编辑母版标题样式' },
8969
9179
  style: {
8970
9180
  left: '50px',
8971
9181
  top: '40px',
@@ -8975,51 +9185,6 @@ export function useSlides() {
8975
9185
  fontWeight: 'bold',
8976
9186
  color: '#1e293b'
8977
9187
  }
8978
- },
8979
- {
8980
- id: 'ph-date-' + Date.now().toString(),
8981
- type: 'text',
8982
- name: '日期占位符',
8983
- content: `<div>${dateStr}</div>`,
8984
- style: {
8985
- left: '50px',
8986
- top: `${H - 48}px`,
8987
- width: '220px',
8988
- height: '30px',
8989
- fontSize: '12px',
8990
- color: '#94a3b8',
8991
- textAlign: 'left'
8992
- }
8993
- },
8994
- {
8995
- id: 'ph-footer-' + Date.now().toString(),
8996
- type: 'text',
8997
- name: '页脚占位符',
8998
- content: '<div>页脚区</div>',
8999
- style: {
9000
- left: `${Math.round(W / 2 - 110)}px`,
9001
- top: `${H - 48}px`,
9002
- width: '220px',
9003
- height: '30px',
9004
- fontSize: '12px',
9005
- color: '#94a3b8',
9006
- textAlign: 'center'
9007
- }
9008
- },
9009
- {
9010
- id: 'ph-num-' + Date.now().toString(),
9011
- type: 'text',
9012
- name: '页码占位符',
9013
- content: '<div>&lt;#&gt;</div>',
9014
- style: {
9015
- left: `${W - 270}px`,
9016
- top: `${H - 48}px`,
9017
- width: '220px',
9018
- height: '30px',
9019
- fontSize: '12px',
9020
- color: '#94a3b8',
9021
- textAlign: 'right'
9022
- }
9023
9188
  }
9024
9189
  ]
9025
9190
 
@@ -9167,14 +9332,17 @@ export function useSlides() {
9167
9332
  type: 'shape',
9168
9333
  name: '图片占位符',
9169
9334
  content: 'rect',
9170
- text: '<div>图片占位符</div>',
9335
+ text: '',
9336
+ isPlaceholder: true,
9337
+ placeholder: { type: 'image', prompt: '图片占位符' },
9171
9338
  style: {
9172
9339
  left: '50px',
9173
9340
  top: '140px',
9174
9341
  width: `${W - 100}px`,
9175
9342
  height: `${Math.max(200, H - 210)}px`,
9176
- fill: '#f1f5f9',
9177
- textColor: '#6366f1',
9343
+ fill: '#d9dde3',
9344
+ borderRadius: '12px',
9345
+ textColor: '#475569',
9178
9346
  fontSize: '16px',
9179
9347
  textAlign: 'center'
9180
9348
  }
@@ -9186,6 +9354,8 @@ export function useSlides() {
9186
9354
  textType: 'smallText',
9187
9355
  name: '页码占位符',
9188
9356
  content: '<div>&lt;#&gt;</div>',
9357
+ isPlaceholder: true,
9358
+ placeholder: { type: 'slide-number', prompt: '<#>' },
9189
9359
  style: {
9190
9360
  left: `${W - 270}px`,
9191
9361
  top: `${H - 48}px`,
@@ -9204,6 +9374,8 @@ export function useSlides() {
9204
9374
  textType: 'smallText',
9205
9375
  name: '日期占位符',
9206
9376
  content: `<div>${dateStr}</div>`,
9377
+ isPlaceholder: true,
9378
+ placeholder: { type: 'date', prompt: dateStr },
9207
9379
  style: {
9208
9380
  left: '50px',
9209
9381
  top: `${H - 48}px`,
@@ -9220,6 +9392,8 @@ export function useSlides() {
9220
9392
  type: 'text',
9221
9393
  name: '页脚占位符',
9222
9394
  content: '<div>页脚文本描述</div>',
9395
+ isPlaceholder: true,
9396
+ placeholder: { type: 'footer', prompt: '页脚文本描述' },
9223
9397
  style: {
9224
9398
  left: `${Math.round(W / 2 - 110)}px`,
9225
9399
  top: `${H - 48}px`,
@@ -9233,7 +9407,10 @@ export function useSlides() {
9233
9407
  }
9234
9408
 
9235
9409
  if (!targetObj.elements) targetObj.elements = []
9236
- if (newEl) newEl.isMaster = true
9410
+ if (newEl) {
9411
+ newEl.isMaster = true
9412
+ newEl.isPlaceholder = true
9413
+ }
9237
9414
  targetObj.elements.push(newEl)
9238
9415
  selectedElementId.value = newId
9239
9416
  selectedElementIds.value = [newId]
@@ -9354,6 +9531,7 @@ export function useSlides() {
9354
9531
  updateAnimationDuration,
9355
9532
  updateAnimationTrigger,
9356
9533
  updateAnimationEffect,
9534
+ updateAnimationTargetElement,
9357
9535
  updateAnimationOrder,
9358
9536
  currentAnimationIndex,
9359
9537
  execNext,