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
@@ -101,7 +101,8 @@ const effectiveMasterElements = computed(() => {
101
101
  if (isMasterEditingView?.value) {
102
102
  if (activeLayoutId?.value) {
103
103
  const master = slideMasters?.value?.find((m: any) => m.id === activeMasterId.value) || slideMasters?.value?.[0]
104
- return (master?.elements || []).filter((el: any) => !isPlaceholderElement(el))
104
+ const layoutElIds = new Set((activeSlide?.value?.elements || []).map((e: any) => e.id))
105
+ return (master?.elements || []).filter((el: any) => !isPlaceholderElement(el) && !layoutElIds.has(el.id))
105
106
  }
106
107
  return []
107
108
  }
@@ -818,7 +819,8 @@ const {
818
819
  updateLineGeometry(el, start, end, absControls, allElements, parentGroupDim)
819
820
  },
820
821
  selectedElementWidth,
821
- selectedElementHeight
822
+ selectedElementHeight,
823
+ activeSlideVersion
822
824
  })
823
825
 
824
826
  const isInteracting = computed(() => {
@@ -1026,9 +1028,6 @@ const getOverlayHeight = (el: any) => {
1026
1028
  if (isDragging.value && el?.id && activeDragDimensions.has(el.id)) {
1027
1029
  return `${activeDragDimensions.get(el.id)!.height}px`
1028
1030
  }
1029
- if (el.id === selectedElementId.value && !isResizing.value && selectedElementHeight.value && selectedElementHeight.value !== '0px') {
1030
- return selectedElementHeight.value
1031
- }
1032
1031
  // For table elements: check DOM rendered table height if available
1033
1032
  if (el.type === 'text' && el.content?.includes('<table') && !isDragging.value) {
1034
1033
  const container = document.getElementById(`editable-${el.id}`) || document.getElementById(`canvas-el-${el.id}`)
@@ -1185,6 +1184,29 @@ const getFloatingQuickToolbarStyle = (el: any) => {
1185
1184
 
1186
1185
  let selectedElementResizeObserver: ResizeObserver | null = null
1187
1186
 
1187
+ function syncSelectedElementDimensionsFromDom(id: string | null) {
1188
+ if (!id) {
1189
+ selectedElementWidth.value = '0px'
1190
+ selectedElementHeight.value = '0px'
1191
+ return null
1192
+ }
1193
+ const dom = document.getElementById(`canvas-el-${id}`)
1194
+ if (!dom) {
1195
+ selectedElementWidth.value = '0px'
1196
+ selectedElementHeight.value = '0px'
1197
+ return null
1198
+ }
1199
+ const tableEl = dom.querySelector('table')
1200
+ const editableDom = document.getElementById(`editable-${id}`)
1201
+ const selEl = activeSlide.value?.elements.find((x: any) => x.id === id)
1202
+ const isVertical = selEl && (selEl.style?.writingMode === 'vertical-rl' || selEl.style?.writingMode === 'vertical-lr')
1203
+ const initialW = isVertical && editableDom ? Math.max(editableDom.scrollWidth, dom.offsetWidth) : dom.offsetWidth
1204
+ const initialH = dom.offsetHeight > 0 ? dom.offsetHeight : (tableEl && tableEl.offsetHeight > 0 ? tableEl.offsetHeight : 0)
1205
+ selectedElementWidth.value = `${initialW}px`
1206
+ selectedElementHeight.value = `${initialH}px`
1207
+ return { dom, tableEl, editableDom, isVertical }
1208
+ }
1209
+
1188
1210
  function observeSelectedElement() {
1189
1211
  if (selectedElementResizeObserver) {
1190
1212
  selectedElementResizeObserver.disconnect()
@@ -1197,6 +1219,9 @@ function observeSelectedElement() {
1197
1219
  return
1198
1220
  }
1199
1221
 
1222
+ // 同步测量一次:若 DOM 已存在(如未选中状态下直接按住拖拽选中),立即更新尺寸,避免 0px 或残留上一个选中元素的尺寸
1223
+ syncSelectedElementDimensionsFromDom(selectedElementId.value)
1224
+
1200
1225
  nextTick(() => {
1201
1226
  const dom = document.getElementById(`canvas-el-${selectedElementId.value}`)
1202
1227
  if (dom) {
@@ -2167,9 +2192,14 @@ function isPlaceholderElement(el: any): boolean {
2167
2192
  const isMarkedAsPlaceholder = Boolean(
2168
2193
  el.isPlaceholder ||
2169
2194
  el.placeholder ||
2170
- (el.id && typeof el.id === 'string' && el.id.startsWith('ph-')) ||
2195
+ (el.id && typeof el.id === 'string' && (el.id.startsWith('ph-') || el.id.startsWith('ph_'))) ||
2171
2196
  (el.name && typeof el.name === 'string' && el.name.includes('占位符'))
2172
2197
  )
2198
+ if (isMarkedAsPlaceholder) {
2199
+ if (isMasterEditingView?.value || el.isPlaceholder || el.placeholder || (el.id && (el.id.startsWith('ph-') || el.id.startsWith('ph_')))) {
2200
+ return true
2201
+ }
2202
+ }
2173
2203
  if (el.type === 'text') {
2174
2204
  const text = (el.content || '').replace(/<[^>]*>/g, '').replace(/&nbsp;/g, ' ').trim()
2175
2205
  if (!text) {
@@ -2212,6 +2242,7 @@ function isPlaceholderElement(el: any): boolean {
2212
2242
  '编辑文本样式',
2213
2243
  '页脚 / 日期文本',
2214
2244
  '页脚占位符',
2245
+ '页脚区',
2215
2246
  '页码占位符',
2216
2247
  '日期占位符',
2217
2248
  '<#>',
@@ -2485,8 +2516,10 @@ function handleCanvasDblClick(e: MouseEvent) {
2485
2516
  const pctY = (localY / logicalHeight.value) * 100
2486
2517
 
2487
2518
  const recipe = getActiveSlideThemeRecipe(activeSlide.value)
2488
- const textColor = recipe.textStyles?.title?.color || recipe.styleColors?.[0] || globalFontColor.value || recipe.textStyles?.normalText?.color || '#0f172a'
2489
- const textFont = recipe.styleFonts?.bodyFont || recipe.styleFonts?.titleFont || globalFontFamily.value || 'PingFang SC'
2519
+ const textColor = globalFontColor.value || recipe.textStyles?.title?.color || recipe.styleColors?.[0] || recipe.textStyles?.normalText?.color || '#0f172a'
2520
+ const textFont = (globalFontFamily.value && globalFontFamily.value !== '默认字体')
2521
+ ? globalFontFamily.value
2522
+ : (recipe.styleFonts?.bodyFont || recipe.styleFonts?.titleFont || 'SourceHanSansCN')
2490
2523
 
2491
2524
  const newTextEl = {
2492
2525
  id: 'text-' + Date.now().toString() + Math.random().toString(36).substring(2, 7),
@@ -2682,10 +2715,12 @@ function handleCanvasMouseDown(e: MouseEvent) {
2682
2715
  const typeConf = TEXT_TYPE_CONFIGS[textType] || TEXT_TYPE_CONFIGS.normalText
2683
2716
  const textCfg = recipe.textStyles?.[textType] || defaultStyleRecipe.textStyles[textType] || defaultStyleRecipe.textStyles.normalText
2684
2717
  const isHeading = textType === 'title' || textType === 'headline'
2685
- const textFont = textCfg.fontFamily || (isHeading
2686
- ? (recipe.styleFonts?.titleFont && recipe.styleFonts.titleFont !== '默认字体' ? recipe.styleFonts.titleFont : 'SourceHanSansCN')
2687
- : (recipe.styleFonts?.bodyFont && recipe.styleFonts.bodyFont !== '默认字体' ? recipe.styleFonts.bodyFont : (globalFontFamily.value && globalFontFamily.value !== '默认字体' ? globalFontFamily.value : 'SourceHanSansCN')))
2688
- const textColor = textCfg.color || (textType === 'smallText' ? '#64748b' : (isHeading ? (recipe.textStyles?.title?.color || '#0f172a') : (recipe.textStyles?.normalText?.color || '#334155')))
2718
+ const textFont = (globalFontFamily.value && globalFontFamily.value !== '默认字体')
2719
+ ? globalFontFamily.value
2720
+ : (textCfg.fontFamily || (isHeading
2721
+ ? (recipe.styleFonts?.titleFont && recipe.styleFonts.titleFont !== '默认字体' ? recipe.styleFonts.titleFont : 'SourceHanSansCN')
2722
+ : (recipe.styleFonts?.bodyFont && recipe.styleFonts.bodyFont !== '默认字体' ? recipe.styleFonts.bodyFont : 'SourceHanSansCN')))
2723
+ const textColor = globalFontColor.value || textCfg.color || (textType === 'smallText' ? '#64748b' : (isHeading ? (recipe.textStyles?.title?.color || '#0f172a') : (recipe.textStyles?.normalText?.color || '#334155')))
2689
2724
  const fontSize = `${textCfg.fontSize || typeConf.defaultFontSize}px`
2690
2725
  const fontWeight = textCfg.fontWeight || typeConf.defaultFontWeight
2691
2726
  const lineHeight = textCfg.lineHeight || typeConf.defaultLineHeight
@@ -2729,10 +2764,12 @@ function handleCanvasMouseDown(e: MouseEvent) {
2729
2764
  const typeConf = TEXT_TYPE_CONFIGS[textType] || TEXT_TYPE_CONFIGS.normalText
2730
2765
  const textCfg = recipe.textStyles?.[textType] || defaultStyleRecipe.textStyles[textType] || defaultStyleRecipe.textStyles.normalText
2731
2766
  const isHeading = textType === 'title' || textType === 'headline'
2732
- const textFont = textCfg.fontFamily || (isHeading
2733
- ? (recipe.styleFonts?.titleFont && recipe.styleFonts.titleFont !== '默认字体' ? recipe.styleFonts.titleFont : 'SourceHanSansCN')
2734
- : (recipe.styleFonts?.bodyFont && recipe.styleFonts.bodyFont !== '默认字体' ? recipe.styleFonts.bodyFont : (globalFontFamily.value && globalFontFamily.value !== '默认字体' ? globalFontFamily.value : 'SourceHanSansCN')))
2735
- const textColor = textCfg.color || (textType === 'smallText' ? '#64748b' : (isHeading ? (recipe.textStyles?.title?.color || '#0f172a') : (recipe.textStyles?.normalText?.color || '#334155')))
2767
+ const textFont = (globalFontFamily.value && globalFontFamily.value !== '默认字体')
2768
+ ? globalFontFamily.value
2769
+ : (textCfg.fontFamily || (isHeading
2770
+ ? (recipe.styleFonts?.titleFont && recipe.styleFonts.titleFont !== '默认字体' ? recipe.styleFonts.titleFont : 'SourceHanSansCN')
2771
+ : (recipe.styleFonts?.bodyFont && recipe.styleFonts.bodyFont !== '默认字体' ? recipe.styleFonts.bodyFont : 'SourceHanSansCN')))
2772
+ const textColor = globalFontColor.value || textCfg.color || (textType === 'smallText' ? '#64748b' : (isHeading ? (recipe.textStyles?.title?.color || '#0f172a') : (recipe.textStyles?.normalText?.color || '#334155')))
2736
2773
  const fontSize = `${textCfg.fontSize || typeConf.defaultFontSize}px`
2737
2774
  const fontWeight = textCfg.fontWeight || typeConf.defaultFontWeight
2738
2775
  const lineHeight = textCfg.lineHeight || typeConf.defaultLineHeight
@@ -2775,9 +2812,11 @@ function handleCanvasMouseDown(e: MouseEvent) {
2775
2812
  const shapeFill = recipe.shape?.fillColor || (recipe.styleColors && recipe.styleColors[0]) || '#2563eb'
2776
2813
  const shapeBorder = recipe.shape?.borderColor || (recipe.styleColors && recipe.styleColors[1]) || '#1d4ed8'
2777
2814
  const shapeTextCol = recipe.shape?.textColor || '#ffffff'
2778
- const shapeFont = (recipe.styleFonts?.bodyFont && recipe.styleFonts.bodyFont !== '默认字体')
2779
- ? recipe.styleFonts.bodyFont
2780
- : 'SourceHanSansCN'
2815
+ const shapeFont = (globalFontFamily.value && globalFontFamily.value !== '默认字体')
2816
+ ? globalFontFamily.value
2817
+ : ((recipe.styleFonts?.bodyFont && recipe.styleFonts.bodyFont !== '默认字体')
2818
+ ? recipe.styleFonts.bodyFont
2819
+ : 'SourceHanSansCN')
2781
2820
 
2782
2821
  style = {
2783
2822
  top: `${creationStartPctY.toFixed(2)}%`,
@@ -2816,6 +2855,7 @@ function handleCanvasMouseDown(e: MouseEvent) {
2816
2855
  borderWidth: '2px',
2817
2856
  borderStyle: 'solid',
2818
2857
  textColor: shapeTextCol,
2858
+ fontFamily: shapeFont,
2819
2859
  viewBox: pendingShapeViewBox.value,
2820
2860
  pathFormula,
2821
2861
  keypoints
@@ -3579,10 +3619,14 @@ function handleElementMouseDown(e: MouseEvent, el: any) {
3579
3619
  return
3580
3620
  }
3581
3621
 
3582
- // 核心保护:若当前元素正在进行文字编辑,且鼠标按在编辑文本区域内,放行浏览器原生光标定位与拖动划词选择,绝不触发元素拖动
3622
+ // 核心保护:若当前元素正在进行文字编辑,且鼠标按在实际文字字符上(或形状编辑区域内),放行浏览器原生光标定位与拖动划词选择;若按在文本框空白区域则允许拖动文本框
3583
3623
  if (isEditingText.value && (selectedElementId.value === el.id || editingElementId.value === el.id)) {
3584
3624
  const target = e.target as HTMLElement
3585
- if (target && (target.closest(`[id^="editable-${el.id}"]`) || target.closest('.shape-text-overlay-wrapper') || el.type === 'shape')) {
3625
+ if (el.type === 'text' && !el.latex && !el.content?.includes('<table')) {
3626
+ if (isClickOnTextCharacters(e, `editable-${el.id}`)) {
3627
+ return
3628
+ }
3629
+ } else if (target && (target.closest(`[id^="editable-${el.id}"]`) || target.closest('.shape-text-overlay-wrapper') || el.type === 'shape')) {
3586
3630
  return
3587
3631
  }
3588
3632
  }
@@ -3782,15 +3826,26 @@ function startChildDrag(e: MouseEvent, child: any, parentGroup: any) {
3782
3826
  if (isReadonly.value) return
3783
3827
  const childContainerEl = document.getElementById(`editable-${child.id}`)
3784
3828
  const childCanvasElDom = document.getElementById(`canvas-el-${child.id}`)
3785
- const isChildClickInside = (childContainerEl && (childContainerEl === e.target || childContainerEl.contains(e.target as Node))) ||
3786
- (childCanvasElDom && (childCanvasElDom === e.target || childCanvasElDom.contains(e.target as Node)))
3829
+ const isChildClickInside = child.type === 'text' && !child.latex && !child.content?.includes('<table')
3830
+ ? isClickOnTextCharacters(e, `editable-${child.id}`)
3831
+ : ((childContainerEl && (childContainerEl === e.target || childContainerEl.contains(e.target as Node))) ||
3832
+ (childCanvasElDom && (childCanvasElDom === e.target || childCanvasElDom.contains(e.target as Node))))
3787
3833
 
3788
3834
  if (isEditingText.value && selectedElementId.value === child.id && isChildClickInside) {
3789
3835
  return
3790
3836
  }
3791
3837
 
3838
+ if (child.type === 'text' && !child.latex && !child.content?.includes('<table') && isChildClickInside && !e.ctrlKey && !e.shiftKey) {
3839
+ selectedElementId.value = child.id
3840
+ selectedElementIds.value = [child.id]
3841
+ if (!isEditingText.value) {
3842
+ startEditingTextElement(child.id, e)
3843
+ }
3844
+ return
3845
+ }
3846
+
3792
3847
  if (isEditingText.value && !isChildClickInside) {
3793
- checkAndExitEditText(undefined, true)
3848
+ checkAndExitEditText(child, true)
3794
3849
  }
3795
3850
  e.preventDefault()
3796
3851
  pauseHistory.value = true
@@ -3982,15 +4037,17 @@ function handleChildMouseDown(e: MouseEvent, child: any, parentGroup: any) {
3982
4037
  if (activeTool.value) return
3983
4038
  const childContainerEl = document.getElementById(`editable-${child.id}`)
3984
4039
  const childCanvasElDom = document.getElementById(`canvas-el-${child.id}`)
3985
- const isChildClickInside = (childContainerEl && (childContainerEl === e.target || childContainerEl.contains(e.target as Node))) ||
3986
- (childCanvasElDom && (childCanvasElDom === e.target || childCanvasElDom.contains(e.target as Node)))
4040
+ const isChildClickInside = child.type === 'text' && !child.latex && !child.content?.includes('<table')
4041
+ ? isClickOnTextCharacters(e, `editable-${child.id}`)
4042
+ : ((childContainerEl && (childContainerEl === e.target || childContainerEl.contains(e.target as Node))) ||
4043
+ (childCanvasElDom && (childCanvasElDom === e.target || childCanvasElDom.contains(e.target as Node))))
3987
4044
 
3988
4045
  if (isEditingText.value && selectedElementId.value === child.id && isChildClickInside) {
3989
4046
  return
3990
4047
  }
3991
4048
 
3992
4049
  if (isEditingText.value && !isChildClickInside) {
3993
- checkAndExitEditText(undefined, true)
4050
+ checkAndExitEditText(child, true)
3994
4051
  }
3995
4052
  e.stopPropagation()
3996
4053
  if (isReadonly.value) return
@@ -4087,6 +4144,9 @@ function handleChildClick(e: MouseEvent, child: any, parentGroup: any) {
4087
4144
  } else {
4088
4145
  selectedElementId.value = child.id
4089
4146
  selectedElementIds.value = [child.id]
4147
+ if (child.type === 'text' && !child.content?.includes('<table') && !child.latex && isClickOnTextCharacters(e, `editable-${child.id}`)) {
4148
+ startEditingTextElement(child.id, e)
4149
+ }
4090
4150
  }
4091
4151
  }
4092
4152
  }
@@ -4158,7 +4218,7 @@ function handleElementClick(e: MouseEvent, elId: string) {
4158
4218
  selectElement(elId, e)
4159
4219
  }
4160
4220
 
4161
- // Check if they clicked the actual text content rather than the draggable border handles
4221
+ // Check if they clicked the actual text content rather than blank areas or draggable border handles
4162
4222
  const target = e.target as HTMLElement
4163
4223
  const el = activeSlide.value?.elements.find((x: any) => x.id === elId)
4164
4224
  if (el) {
@@ -4173,9 +4233,12 @@ function handleElementClick(e: MouseEvent, elId: string) {
4173
4233
  const isEmptyText = !textVal
4174
4234
  const isPlaceholder = isPlaceholderElement(el)
4175
4235
  const isClickingTextContent = target && (
4176
- (target.closest('.shape-text-overlay') && (isEmptyText || isPlaceholder || isClickOnTextCharacters(e, `editable-${el.id}`))) ||
4177
- (target.closest('[id^="editable-"]') && !target.closest('.text-edit-drag-helper') && (isEmptyText || isPlaceholder || isClickOnTextCharacters(e, `editable-${el.id}`))) ||
4178
- (isPlaceholder && target.closest('.canvas-element') && !target.closest('.text-edit-drag-helper'))
4236
+ el.type === 'text'
4237
+ ? (!target.closest('.text-edit-drag-helper') && isClickOnTextCharacters(e, `editable-${el.id}`))
4238
+ : (
4239
+ (target.closest('.shape-text-overlay') && (isEmptyText || isPlaceholder || isClickOnTextCharacters(e, `editable-${el.id}`))) ||
4240
+ (isPlaceholder && target.closest('.canvas-element') && !target.closest('.text-edit-drag-helper'))
4241
+ )
4179
4242
  )
4180
4243
 
4181
4244
  if (isClickingTextContent && (!isEditingText.value || selectedElementId.value !== el.id) && !e.ctrlKey && !e.shiftKey) {
@@ -4271,16 +4334,26 @@ function getElementWidthHeight(el: any): { width: number; height: number } {
4271
4334
  // 关键:拖拽期间绝不查询 DOM,杜绝强制重排
4272
4335
  const dom = isDragging.value ? null : (document.getElementById(`canvas-el-${el.id}`) || document.getElementById(`editable-${el.id}`))
4273
4336
 
4337
+ let usedSelectedWidth = false
4338
+ let usedSelectedHeight = false
4274
4339
  if (el.id === selectedElementId.value && !isResizing.value && el.type !== 'group') {
4275
4340
  if (selectedElementWidth.value && selectedElementWidth.value !== '0px') {
4276
4341
  const parsedW = parseFloat(selectedElementWidth.value)
4277
- if (!isNaN(parsedW) && parsedW > 0) width = parsedW
4342
+ if (!isNaN(parsedW) && parsedW > 0) {
4343
+ width = parsedW
4344
+ usedSelectedWidth = true
4345
+ }
4278
4346
  }
4279
4347
  if (selectedElementHeight.value && selectedElementHeight.value !== '0px') {
4280
4348
  const parsedH = parseFloat(selectedElementHeight.value)
4281
- if (!isNaN(parsedH) && parsedH > 0) height = parsedH
4349
+ if (!isNaN(parsedH) && parsedH > 0) {
4350
+ height = parsedH
4351
+ usedSelectedHeight = true
4352
+ }
4282
4353
  }
4283
- } else {
4354
+ }
4355
+
4356
+ if (!usedSelectedWidth) {
4284
4357
  if (isWidthAuto && dom) {
4285
4358
  const isVertical = el.type === 'text' && (el.style?.writingMode === 'vertical-rl' || el.style?.writingMode === 'vertical-lr')
4286
4359
  const editableDom = document.getElementById(`editable-${el.id}`)
@@ -4292,7 +4365,9 @@ function getElementWidthHeight(el: any): { width: number; height: number } {
4292
4365
  width = parseFloat(wStr) || 200
4293
4366
  }
4294
4367
  }
4368
+ }
4295
4369
 
4370
+ if (!usedSelectedHeight) {
4296
4371
  if (isHeightAuto && dom) {
4297
4372
  height = dom.offsetHeight
4298
4373
  } else if (el.style?.height !== undefined && el.style.height !== 'auto') {
@@ -4304,6 +4379,8 @@ function getElementWidthHeight(el: any): { width: number; height: number } {
4304
4379
  } else if (el.type === 'text') {
4305
4380
  if (dom) {
4306
4381
  height = dom.offsetHeight
4382
+ } else if (typeof el.height === 'number' && el.height > 0) {
4383
+ height = el.height
4307
4384
  } else {
4308
4385
  height = 40
4309
4386
  }
@@ -4460,59 +4537,92 @@ function isClickOnTextCharacters(e: MouseEvent, containerId: string): boolean {
4460
4537
  if (!container) return false
4461
4538
 
4462
4539
  const target = e.target as HTMLElement
4463
- if (target && container.contains(target) && !target.closest('.text-edit-drag-helper')) {
4464
- return true
4540
+ if (target && target.closest('.text-edit-drag-helper')) {
4541
+ return false
4465
4542
  }
4466
4543
 
4467
- if (typeof (document as any).caretRangeFromPoint === 'function') {
4468
- try {
4469
- const range = (document as any).caretRangeFromPoint(e.clientX, e.clientY)
4470
- if (range && range.startContainer && container.contains(range.startContainer)) {
4544
+ const x = e.clientX
4545
+ const y = e.clientY
4546
+ const tolerance = 2
4547
+
4548
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null)
4549
+ const range = document.createRange()
4550
+ let node: Node | null
4551
+ while ((node = walker.nextNode())) {
4552
+ const text = node.textContent || ''
4553
+ if (!text.replace(/[\r\n\u200B]/g, '').length) continue
4554
+ range.selectNodeContents(node)
4555
+ const rects = range.getClientRects()
4556
+ for (let i = 0; i < rects.length; i++) {
4557
+ const rect = rects[i]
4558
+ if (rect.width <= 0 || rect.height <= 0) continue
4559
+ if (
4560
+ x >= rect.left - tolerance &&
4561
+ x <= rect.right + tolerance &&
4562
+ y >= rect.top - tolerance &&
4563
+ y <= rect.bottom + tolerance
4564
+ ) {
4471
4565
  return true
4472
4566
  }
4473
- } catch (_) {}
4567
+ }
4568
+ }
4569
+
4570
+ // 当处于文本编辑状态时,若存在空行(<br>),允许在空行光标位置附近点击定位,其余空白区域保持可拖拽
4571
+ if (isEditingText.value) {
4572
+ const brs = container.querySelectorAll('br')
4573
+ for (let i = 0; i < brs.length; i++) {
4574
+ const rect = brs[i].getBoundingClientRect()
4575
+ if (rect.height > 0 && y >= rect.top - tolerance && y <= rect.bottom + tolerance) {
4576
+ if (x >= rect.left - 4 && x <= rect.left + Math.max(rect.width, 8) + 4) {
4577
+ return true
4578
+ }
4579
+ }
4580
+ }
4474
4581
  }
4475
4582
 
4476
4583
  return false
4477
4584
  }
4478
4585
 
4479
4586
  function handleTextMouseMove(e: MouseEvent, el: any) {
4480
- if (isDragging.value || isCanvasDragging.value) return
4587
+ if (isReadonly.value || isDragging.value || isCanvasDragging.value) return
4481
4588
  if (el.latex || el.content?.includes('<table')) return
4482
4589
  const containerId = `editable-${el.id}`
4483
4590
  const container = document.getElementById(containerId)
4484
- if (isEditingText.value && (selectedElementId.value === el.id || editingElementId?.value === el.id)) {
4485
- if (container) {
4486
- container.style.cursor = 'text'
4487
- }
4488
- return
4489
- }
4591
+ const canvasElDom = document.getElementById(`canvas-el-${el.id}`)
4490
4592
  if (!container) return
4491
4593
 
4492
- const textVal = el.type === 'shape'
4493
- ? (el.text ? String(el.text).replace(/<[^>]*>/g, '').trim() : '')
4494
- : (el.content ? String(el.content).replace(/<[^>]*>/g, '').trim() : '')
4495
- const isEmptyText = !textVal
4496
- const isPlaceholder = isPlaceholderElement(el)
4497
-
4498
- if (isEmptyText || isPlaceholder) {
4499
- container.style.cursor = 'text'
4500
- return
4594
+ if (el.type === 'shape') {
4595
+ if (isEditingText.value && (selectedElementId.value === el.id || editingElementId?.value === el.id)) {
4596
+ container.style.cursor = 'text'
4597
+ return
4598
+ }
4599
+ const textVal = el.text ? String(el.text).replace(/<[^>]*>/g, '').trim() : ''
4600
+ const isEmptyText = !textVal
4601
+ const isPlaceholder = isPlaceholderElement(el)
4602
+ if (isEmptyText || isPlaceholder) {
4603
+ container.style.cursor = 'text'
4604
+ return
4605
+ }
4501
4606
  }
4502
4607
 
4503
4608
  const isOverText = isClickOnTextCharacters(e, containerId)
4504
- if (isOverText) {
4505
- container.style.cursor = 'text'
4506
- } else {
4507
- container.style.cursor = 'move'
4609
+ const nextCursor = isOverText ? 'text' : 'move'
4610
+ container.style.cursor = nextCursor
4611
+ if (canvasElDom) {
4612
+ canvasElDom.style.cursor = nextCursor
4508
4613
  }
4509
4614
  }
4510
4615
 
4511
4616
  function handleTextMouseLeave(_e: MouseEvent, el: any) {
4617
+ if (isReadonly.value) return
4512
4618
  const container = document.getElementById(`editable-${el.id}`)
4513
- if (container && (!isEditingText.value || selectedElementId.value !== el.id)) {
4619
+ const canvasElDom = document.getElementById(`canvas-el-${el.id}`)
4620
+ if (container) {
4514
4621
  container.style.cursor = 'move'
4515
4622
  }
4623
+ if (canvasElDom) {
4624
+ canvasElDom.style.cursor = 'move'
4625
+ }
4516
4626
  }
4517
4627
 
4518
4628
  function startDrag(e: MouseEvent, el: any) {
@@ -4520,8 +4630,10 @@ function startDrag(e: MouseEvent, el: any) {
4520
4630
  const target = e.target as HTMLElement
4521
4631
  const container = document.getElementById(`editable-${el.id}`)
4522
4632
  const canvasElDom = document.getElementById(`canvas-el-${el.id}`)
4523
- const isInsideEditingContainer = (container && (container === target || container.contains(target))) ||
4524
- (canvasElDom && (canvasElDom === target || canvasElDom.contains(target)))
4633
+ const isInsideEditingContainer = el.type === 'text' && !el.latex && !el.content?.includes('<table')
4634
+ ? isClickOnTextCharacters(e, `editable-${el.id}`)
4635
+ : ((container && (container === target || container.contains(target))) ||
4636
+ (canvasElDom && (canvasElDom === target || canvasElDom.contains(target))))
4525
4637
  if (isEditingText.value && selectedElementId.value === el.id && isInsideEditingContainer) {
4526
4638
  return
4527
4639
  }
@@ -4532,11 +4644,14 @@ function startDrag(e: MouseEvent, el: any) {
4532
4644
  const isEmptyText = !textVal
4533
4645
  const isPlaceholder = isPlaceholderElement(el)
4534
4646
 
4535
- // Check if they clicked inside the actual text characters (or if it's an empty text box or placeholder)
4647
+ // Check if they clicked inside the actual text characters
4536
4648
  const isClickingTextContent = target && (
4537
- (target.closest('.shape-text-overlay') && (isEmptyText || isPlaceholder || isClickOnTextCharacters(e, `editable-${el.id}`))) ||
4538
- (target.closest('[id^="editable-"]') && !target.closest('.text-edit-drag-helper') && (isEmptyText || isPlaceholder || isClickOnTextCharacters(e, `editable-${el.id}`))) ||
4539
- (isPlaceholder && target.closest('.canvas-element') && !target.closest('.text-edit-drag-helper'))
4649
+ el.type === 'text'
4650
+ ? (!target.closest('.text-edit-drag-helper') && isClickOnTextCharacters(e, `editable-${el.id}`))
4651
+ : (
4652
+ (target.closest('.shape-text-overlay') && (isEmptyText || isPlaceholder || isClickOnTextCharacters(e, `editable-${el.id}`))) ||
4653
+ (isPlaceholder && target.closest('.canvas-element') && !target.closest('.text-edit-drag-helper'))
4654
+ )
4540
4655
  )
4541
4656
 
4542
4657
  if (isClickingTextContent && !e.ctrlKey && !e.shiftKey) {
@@ -4562,7 +4677,7 @@ function startDrag(e: MouseEvent, el: any) {
4562
4677
  }
4563
4678
 
4564
4679
  e.preventDefault()
4565
- checkAndExitEditText(undefined, true)
4680
+ checkAndExitEditText(el, true)
4566
4681
 
4567
4682
  dragOccurred = false
4568
4683
  hasClonedForCtrlDrag = false
@@ -4589,11 +4704,13 @@ function startDrag(e: MouseEvent, el: any) {
4589
4704
  selectedElementIds.value = [...selectedElementIds.value, ...targetIds.filter(x => !selectedElementIds.value.includes(x))]
4590
4705
  selectedElementId.value = el.id
4591
4706
  }
4707
+ syncSelectedElementDimensionsFromDom(selectedElementId.value)
4592
4708
  } else {
4593
4709
  if (!targetIds.every(tid => selectedElementIds.value.includes(tid))) {
4594
4710
  selectedElementIds.value = targetIds
4595
4711
  selectedElementId.value = el.id
4596
4712
  }
4713
+ syncSelectedElementDimensionsFromDom(selectedElementId.value)
4597
4714
  }
4598
4715
 
4599
4716
  // Abort dragging if any of the selected elements is locked
@@ -4602,11 +4719,7 @@ function startDrag(e: MouseEvent, el: any) {
4602
4719
  return
4603
4720
  }
4604
4721
 
4605
- pauseHistory.value = true
4606
- isCanvasDragging.value = true
4607
- isDragging.value = true
4608
-
4609
- // 录入活跃拖拽尺寸缓存,拖拽全程绝不二次读 DOM
4722
+ // 在开启 isDragging 锁闭 DOM 读取之前,先同步完成活跃拖拽尺寸缓存采集,确保初次按下拖拽时获取到真实 DOM 高度,拖拽全程绝不二次读 DOM
4610
4723
  activeDragDimensions.clear()
4611
4724
 
4612
4725
  // Get initial client coordinates
@@ -4699,6 +4812,10 @@ function startDrag(e: MouseEvent, el: any) {
4699
4812
  }
4700
4813
  })
4701
4814
 
4815
+ pauseHistory.value = true
4816
+ isCanvasDragging.value = true
4817
+ isDragging.value = true
4818
+
4702
4819
  window.addEventListener('mousemove', handleDrag, { capture: true, passive: false } as any)
4703
4820
  window.addEventListener('mouseup', stopDrag, { capture: true } as any)
4704
4821
  window.addEventListener('pointerup', stopDrag, { capture: true } as any)
@@ -5080,8 +5197,8 @@ const selectedElementKeypoints = computed(() => {
5080
5197
  const actualH = geom.height || 200
5081
5198
  const viewBoxW = el.viewBox?.[0] || 200
5082
5199
  const viewBoxH = el.viewBox?.[1] || 200
5083
- const w = viewBoxW
5084
- const h = viewBoxH
5200
+ const w = el.pathFormula === 'roundRect' ? actualW : viewBoxW
5201
+ const h = el.pathFormula === 'roundRect' ? actualH : viewBoxH
5085
5202
 
5086
5203
  return el.keypoints.map((keypoint: any, index: number) => {
5087
5204
  const getBaseSize = formulaObj.getBaseSize?.[index]
@@ -7646,11 +7763,13 @@ async function handleGlobalPaste(e: ClipboardEvent) {
7646
7763
  } else if (block.type === 'text') {
7647
7764
  const textHeightPercent = 8
7648
7765
  const topPos = currentTopPercent
7766
+ const pasteColor = globalFontColor.value || '#000000'
7767
+ const pasteFont = (globalFontFamily.value && globalFontFamily.value !== '默认字体') ? globalFontFamily.value : 'SourceHanSansCN'
7649
7768
 
7650
7769
  const newTextEl = {
7651
7770
  id: 'text-' + Date.now().toString() + Math.random().toString(36).substring(2, 7),
7652
7771
  type: 'text' as const,
7653
- content: `<div>${block.content.replace(/\r\n/g, '<br>').replace(/\n/g, '<br>')}</div>`,
7772
+ content: `<div style="color:${pasteColor}; font-family:${pasteFont};">${block.content.replace(/\r\n/g, '<br>').replace(/\n/g, '<br>')}</div>`,
7654
7773
  style: {
7655
7774
  top: `${topPos.toFixed(2)}%`,
7656
7775
  left: `${leftPercent}%`,
@@ -7658,8 +7777,13 @@ async function handleGlobalPaste(e: ClipboardEvent) {
7658
7777
  height: 'auto',
7659
7778
  rotate: 0,
7660
7779
  fontSize: '18px',
7661
- color: '#000000'
7662
- }
7780
+ color: pasteColor,
7781
+ textColor: pasteColor,
7782
+ fontFamily: pasteFont
7783
+ },
7784
+ color: pasteColor,
7785
+ textColor: pasteColor,
7786
+ fontFamily: pasteFont
7663
7787
  }
7664
7788
 
7665
7789
  currentTopPercent += textHeightPercent + gapPercent
@@ -8030,7 +8154,7 @@ async function handleGlobalPaste(e: ClipboardEvent) {
8030
8154
  : (el.type === 'line'
8031
8155
  ? `${el.style?.left}_${el.style?.top}_${el.style?.width}_${el.style?.height}_${el.start?.join(',')}_${el.end?.join(',')}_${el.broken?.join(',')}_${el.broken2?.join(',')}_${el.curve?.join(',')}`
8032
8156
  : 'frozen'))
8033
- : `${el.id}_${el.style?.left}_${el.style?.top}_${el.style?.width}_${el.style?.height}_${el.style?.rotate}_${el.type === 'line' ? `${el.start?.join(',')}_${el.end?.join(',')}_${el.broken?.join(',')}_${el.broken2?.join(',')}_${el.curve?.join(',')}_` : ''}${selectedElementIds.includes(el.id)}_${selectedElementId === el.id}_${isEditingText}_${activeTab}_${activeSlideVersion}`
8157
+ : `${el.id}_${el.style?.left}_${el.style?.top}_${el.style?.width}_${el.style?.height}_${el.style?.rotate}_${el.type === 'line' ? `${el.start?.join(',')}_${el.end?.join(',')}_${el.broken?.join(',')}_${el.broken2?.join(',')}_${el.curve?.join(',')}_` : ''}${selectedElementIds.includes(el.id)}_${selectedElementId === el.id}_${isEditingText}_${activeTab}_${activeSlideVersion}_${(el.style as any)?.reflection?.enabled}_${(el.style as any)?.reflection?.distance}_${(el.style as any)?.reflection?.size}_${(el.style as any)?.reflection?.blur}_${(el.style as any)?.reflection?.opacity}`
8034
8158
  ]"
8035
8159
  :class="{
8036
8160
  selected: selectedElementIds.includes(el.id),
@@ -8047,7 +8171,7 @@ async function handleGlobalPaste(e: ClipboardEvent) {
8047
8171
  width: (el.type === 'text' && !isMasterOrPlaceholder(el) && (el.autoFit?.type === 'shape' || el.style?.width === 'auto') && !el.content?.includes('<table') && !el.latex && (el.style?.writingMode === 'vertical-rl' || el.style?.writingMode === 'vertical-lr')) ? 'auto' : (el.style?.width || 'auto'),
8048
8172
  height: (el.type === 'text' && !isMasterOrPlaceholder(el) && (el.autoFit?.type === 'shape' || el.style?.height === 'auto') && !el.content?.includes('<table') && !el.latex && el.style?.writingMode !== 'vertical-rl' && el.style?.writingMode !== 'vertical-lr') ? 'auto' : (el.style?.height || (el.height ? `${el.height}px` : 'auto')),
8049
8173
  zIndex: 3,
8050
- cursor: isReadonly ? 'default' : ((isEditingText && (selectedElementId === el.id || editingElementId === el.id) && (el.type === 'text' || el.type === 'shape') && !el.latex && !el.content?.includes('<table')) ? 'text' : undefined),
8174
+ cursor: isReadonly ? 'default' : ((isEditingText && (selectedElementId === el.id || editingElementId === el.id) && el.type === 'shape' && !el.latex && !el.content?.includes('<table')) ? 'text' : undefined),
8051
8175
  transform: el.type === 'line' ? 'none' : `rotate(${el.style?.rotate || 0}deg) scale(${!isTextContainingElement(el) && ((el.style as any)?.flipH || el.flipH || el.isFlipH) ? -1 : 1}, ${!isTextContainingElement(el) && ((el.style as any)?.flipV || el.flipV || el.isFlipV) ? -1 : 1})`,
8052
8176
  opacity: (el.style as any)?.opacity !== undefined ? (el.style as any)?.opacity : 1,
8053
8177
  filter: getElementDropShadowFilter(el),
@@ -8236,6 +8360,8 @@ async function handleGlobalPaste(e: ClipboardEvent) {
8236
8360
  @text-blur="(ev, child) => handleTextBlur(ev, child)"
8237
8361
  @text-input="(ev, child) => handleInput(ev, child)"
8238
8362
  @text-key-down="(ev, child) => handleTextKeyDown(ev, child)"
8363
+ @text-mouse-move="(ev, child) => handleTextMouseMove(ev, child)"
8364
+ @text-mouse-leave="(ev, child) => handleTextMouseLeave(ev, child)"
8239
8365
  @start-editing="(id, ev) => { const target = findElementInSlide(id); if (target && target.type === 'shape') startEditingShapeText(target, ev); else startEditingTextElement(id, ev); }"
8240
8366
  @formula-dbl-click="(child) => handleFormulaDblClick(child)"
8241
8367
  @table-dbl-click="(ev, child) => handleTableDblClick(ev, child)"
@@ -8302,118 +8428,17 @@ async function handleGlobalPaste(e: ClipboardEvent) {
8302
8428
 
8303
8429
  <!-- Shape element -->
8304
8430
  <template v-else-if="el.type === 'shape'">
8305
- <div v-if="el.content === 'circle'" :style="{
8306
- width: '100%',
8307
- height: '100%',
8308
- backgroundColor: el.style.fill !== undefined ? el.style.fill : '#3b82f6',
8309
- borderRadius: '50%',
8310
- borderWidth: el.style.borderWidth || '2px',
8311
- borderStyle: el.style.borderStyle || 'solid',
8312
- borderColor: el.style.borderColor || '#2563eb',
8313
- filter: (el.shadow && el.shadow.type === 'inner') ? `url(#shape-inner-shadow-${el.id})` : ''
8314
- }"></div>
8315
- <div v-else-if="el.content === 'triangle'" style="width: 100%; height: 100%; overflow: visible;">
8316
- <svg viewBox="0 0 100 100" preserveAspectRatio="none"
8317
- style="width: 100%; height: 100%; display: block; overflow: visible;"
8318
- :style="{ filter: (el.shadow && el.shadow.type === 'inner') ? `url(#shape-inner-shadow-${el.id})` : '' }">
8319
- <polygon points="50,0 0,100 100,100"
8320
- :fill="el.style.fill !== undefined ? el.style.fill : '#f59e0b'"
8321
- :stroke="el.style.borderColor || '#d97706'"
8322
- :stroke-width="el.style.borderWidth ? parseInt(el.style.borderWidth) * 1.5 : 3"
8323
- :stroke-dasharray="el.style.borderStyle === 'dashed' ? '5 5' : ''" />
8324
- </svg>
8325
- </div>
8326
- <div v-else-if="el.content && (el.content.trim().startsWith('M') || el.content.trim().startsWith('m'))"
8327
- style="width: 100%; height: 100%; overflow: visible;">
8328
- <svg overflow="visible" style="width: 100%; height: 100%; display: block; overflow: visible;"
8329
- :style="{ filter: (el.shadow && el.shadow.type === 'inner') ? `url(#shape-inner-shadow-${el.id})` : '' }">
8330
- <g
8331
- :transform="el.shapType === 'arc' ? `scale(${getElementWidthHeight(el).width / (el.viewBox ? el.viewBox[0] : 200)}, ${getElementWidthHeight(el).height / (el.viewBox ? el.viewBox[1] : 200)})` : `translate(${(el.style.borderWidth ? parseFloat(el.style.borderWidth) : 2) / 2}, ${(el.style.borderWidth ? parseFloat(el.style.borderWidth) : 2) / 2}) scale(${Math.max(0, getElementWidthHeight(el).width - (el.style.borderWidth ? parseFloat(el.style.borderWidth) : 2)) / (el.viewBox ? el.viewBox[0] : 200)}, ${Math.max(0, getElementWidthHeight(el).height - (el.style.borderWidth ? parseFloat(el.style.borderWidth) : 2)) / (el.viewBox ? el.viewBox[1] : 200)})`">
8332
- <path vector-effect="non-scaling-stroke" :d="el.content"
8333
- :fill="getElementFill(el) !== undefined ? getElementFill(el) : '#3b82f6'"
8334
- :stroke="getElementBorderColor(el) || 'none'" :stroke-width="getElementBorderWidth(el)"
8335
- :stroke-linecap="(el.style?.strokeLinecap || el.borderLineCap || el.lineCap || (el.shapType === 'arc' ? 'round' : undefined)) as any"
8336
- :stroke-dasharray="el.style?.borderStyle === 'dashed' || (el as any).borderType === 'dashed' ? '5 5' : ''" />
8337
- </g>
8338
- </svg>
8339
- </div>
8340
- <div v-else :style="{
8341
- width: '100%',
8342
- height: '100%',
8343
- backgroundColor: (getElementFill(el) !== undefined && getElementFill(el) !== 'none') ? getElementFill(el) : (getElementFill(el) === 'none' ? 'transparent' : '#10b981'),
8344
- borderRadius: '4px',
8345
- borderWidth: `${getElementBorderWidth(el)}px`,
8346
- borderStyle: el.style?.borderStyle || (el as any).borderType || 'solid',
8347
- borderColor: getElementBorderColor(el) || 'transparent',
8348
- filter: (el.shadow && el.shadow.type === 'inner') ? `url(#shape-inner-shadow-${el.id})` : ''
8349
- }"></div>
8350
-
8351
- <div class="shape-text-overlay-wrapper"
8352
- style="position: absolute; display: flex; flex-direction: column; pointer-events: none; box-sizing: border-box;"
8353
- :style="{
8354
- left: isEllipseShape(el) ? '14.6%' : '0px',
8355
- top: isEllipseShape(el) ? '14.6%' : '0px',
8356
- width: isEllipseShape(el) ? '70.8%' : '100%',
8357
- height: isEllipseShape(el) ? '70.8%' : '100%',
8358
- justifyContent: (el.style.verticalAlign === 'top' || el.style.verticalAlign === 'up') ? 'flex-start' : ((el.style.verticalAlign === 'bottom' || el.style.verticalAlign === 'down') ? 'flex-end' : 'center'),
8359
- padding: isEllipseShape(el) ? '0px' : (el.style.padding || '4.8px 9.6px 4.8px 9.6px')
8360
- }">
8361
- <div class="text-reflection-wrapper" style="position: relative; width: 100%; height: auto;">
8362
- <div class="shape-text-overlay" :style="{
8363
- display: 'block',
8364
- width: '100%',
8365
- maxWidth: '100%',
8366
- outline: 'none',
8367
- padding: '0px',
8368
- boxSizing: 'content-box',
8369
- overflow: 'wrap',
8370
- wordBreak: 'break-word',
8371
- pointerEvents: 'none',
8372
- userSelect: 'none',
8373
- color: el.style.textColor || el.style.color || (el.style.fill === '#ffffff' || el.style.fill === '#FFFFFF' ? '#475569' : '#ffffff'),
8374
- fontFamily: el.style.fontFamily || 'inherit',
8375
- fontSize: el.style.fontSize || '16px',
8376
- fontWeight: el.style.fontWeight || ((getSafeShapeText(el).includes('<')) ? 'normal' : 'normal'),
8377
- fontStyle: (el.style as any).fontStyle || 'normal',
8378
- textDecoration: (el.style.textDecoration as any) || 'none',
8379
- lineHeight: el.style.lineHeight || 1.35,
8380
- textAlign: (el.style.textAlign as any) || 'center',
8381
- whiteSpace: (el.style as any).whiteSpace || 'normal',
8382
- '--paragraphSpace': `${(el.style as any).paragraphSpace === undefined ? 3 : (el.style as any).paragraphSpace}px`
8383
- }" v-html="normalizeHtmlFontFamily(getSafeShapeText(el))">
8384
- </div>
8385
-
8386
- <!-- Shape Text Reflection Clone -->
8387
- <div v-if="(el.style as any).textReflection && (el.style as any).textReflection.enabled"
8388
- class="text-element-reflection"
8389
- style="position: absolute; left: 0; width: 100%; pointer-events: none; z-index: 1;" :style="{
8390
- top: `calc(100% + ${(el.style as any).textReflection.distance !== undefined ? (el.style as any).textReflection.distance : 20}px - ${getReflectionLeadingCompensation(el)}px)`,
8391
- transform: 'translateY(100%) scaleY(-1)',
8392
- opacity: (100 - ((el.style as any).textReflection.opacity !== undefined ? (el.style as any).textReflection.opacity : 10)) / 100,
8393
- filter: (el.style as any).textReflection.blur ? `blur(${(((el.style as any).textReflection.blur !== undefined ? (el.style as any).textReflection.blur : 0) * 1.33).toFixed(1)}px)` : 'none',
8394
- ['-webkit-mask-image']: `linear-gradient(to bottom, rgba(0,0,0,1) 0%, rgba(0,0,0,0) ${((el.style as any).textReflection.size !== undefined ? (el.style as any).textReflection.size : 8)}%, rgba(0,0,0,0) 100%)`,
8395
- ['mask-image']: `linear-gradient(to bottom, rgba(0,0,0,1) 0%, rgba(0,0,0,0) ${((el.style as any).textReflection.size !== undefined ? (el.style as any).textReflection.size : 8)}%, rgba(0,0,0,0) 100%)`,
8396
- color: el.style.textColor || el.style.color || (el.style.fill === '#ffffff' || el.style.fill === '#FFFFFF' ? '#475569' : '#ffffff'),
8397
- fontFamily: el.style.fontFamily || 'inherit',
8398
- fontSize: el.style.fontSize || '16px',
8399
- fontWeight: el.style.fontWeight || 'normal',
8400
- fontStyle: (el.style as any).fontStyle || 'normal',
8401
- textDecoration: (el.style.textDecoration as any) || 'none',
8402
- ['-webkit-text-stroke']: getTextStrokeStyle(el),
8403
- ['paint-order']: (el.style as any).enableTextStroke ? 'stroke fill' : 'normal',
8404
- ['text-shadow']: (el.style as any).textShadow || 'none',
8405
- lineHeight: el.style.lineHeight || 1.35,
8406
- textAlign: (el.style.textAlign as any) || 'center',
8407
- whiteSpace: (el.style as any).whiteSpace || 'normal',
8408
- transformOrigin: 'top left',
8409
- height: 'auto'
8410
- }"
8411
- v-html="el.style.textBackground && el.style.textBackground !== 'transparent'
8412
- ? `<span style='background-color: ${el.style.textBackground}; display: inline;'>${normalizeHtmlFontFamily(getSafeShapeText(el))}</span>`
8413
- : normalizeHtmlFontFamily(getSafeShapeText(el))">
8414
- </div>
8415
- </div>
8416
- </div>
8431
+ <CanvasShapeElement
8432
+ :element="el"
8433
+ :active-slide="activeSlide"
8434
+ :is-reflection="true"
8435
+ :theme-recipe="getActiveSlideThemeRecipe(activeSlide)"
8436
+ :normalize-html-font-family="normalizeHtmlFontFamily"
8437
+ :get-text-stroke-style="getTextStrokeStyle"
8438
+ :get-element-text-shadow="getElementTextShadow"
8439
+ :get-reflection-leading-compensation="getReflectionLeadingCompensation"
8440
+ :is-image-placeholder="isImagePlaceholder"
8441
+ />
8417
8442
  </template>
8418
8443
 
8419
8444
  <!-- Image element -->