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
@@ -30,6 +30,9 @@ export function parseColor(colorStr: any) {
30
30
  }
31
31
  }
32
32
  const str = colorStr.trim().toLowerCase()
33
+ if (str === 'transparent' || str === 'none') {
34
+ return { hex: 'transparent', alpha: 0 }
35
+ }
33
36
  if (str.startsWith('rgba')) {
34
37
  const matches = str.match(/rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d.]+)\s*\)/)
35
38
  if (matches) {
@@ -70,15 +73,151 @@ export function hexToRgba(hex: string, alphaPercent: number) {
70
73
  return `rgba(${r}, ${g}, ${b}, ${a})`
71
74
  }
72
75
 
76
+ export function generateGradientEndColor(hex: string): string {
77
+ const parsed = parseColor(hex)
78
+ if (!parsed.hex || parsed.hex === 'transparent' || parsed.hex.toLowerCase() === 'none') return '#60A5FA'
79
+ const clean = parsed.hex.replace('#', '')
80
+ if (clean.length < 6) return '#60A5FA'
81
+ const r = parseInt(clean.substring(0, 2), 16) || 0
82
+ const g = parseInt(clean.substring(2, 4), 16) || 0
83
+ const b = parseInt(clean.substring(4, 6), 16) || 0
84
+
85
+ const brightness = (r * 299 + g * 587 + b * 114) / 1000
86
+ if (brightness > 220) {
87
+ const darkR = Math.max(0, Math.round(r * 0.75)).toString(16).padStart(2, '0')
88
+ const darkG = Math.max(0, Math.round(g * 0.75)).toString(16).padStart(2, '0')
89
+ const darkB = Math.max(0, Math.round(b * 0.75)).toString(16).padStart(2, '0')
90
+ return `#${darkR}${darkG}${darkB}`.toUpperCase()
91
+ } else {
92
+ const lightR = Math.min(255, Math.round(r + (255 - r) * 0.55)).toString(16).padStart(2, '0')
93
+ const lightG = Math.min(255, Math.round(g + (255 - g) * 0.55)).toString(16).padStart(2, '0')
94
+ const lightB = Math.min(255, Math.round(b + (255 - b) * 0.55)).toString(16).padStart(2, '0')
95
+ return `#${lightR}${lightG}${lightB}`.toUpperCase()
96
+ }
97
+ }
98
+
99
+ function encodeSvgColor(color: string, fallback = '#3B82F6'): string {
100
+ const c = (color || fallback).trim()
101
+ if (!c || c === 'none') return 'transparent'
102
+ return c.replace(/#/g, '%23')
103
+ }
104
+
105
+ export function buildPatternSvgDataUrl(type: string, fgColor = '#3B82F6', bgColor = '#FFFFFF'): string {
106
+ const fg = encodeSvgColor(fgColor, '#3B82F6')
107
+ const bg = encodeSvgColor(bgColor, '#FFFFFF')
108
+ const rawFg = (fgColor || '#3B82F6').trim().replace(/#/g, '%23')
109
+ const rawBg = (bgColor || '#FFFFFF').trim().replace(/#/g, '%23')
110
+
111
+ let w = 10
112
+ let h = 10
113
+ let body = ''
114
+
115
+ switch (type) {
116
+ case 'dots-sparse':
117
+ w = 10; h = 10
118
+ body = `<circle cx='2.5' cy='2.5' r='1.1' fill='${fg}'/><circle cx='7.5' cy='7.5' r='1.1' fill='${fg}'/>`
119
+ break
120
+ case 'dots-dense':
121
+ w = 10; h = 10
122
+ body = `<circle cx='5' cy='5' r='2' fill='${fg}'/>`
123
+ break
124
+ case 'diag-up':
125
+ w = 10; h = 10
126
+ body = `<path d='M0 10L10 0M-2 2L2 -2M8 12L12 8' stroke='${fg}' stroke-width='1.5'/>`
127
+ break
128
+ case 'diag-down':
129
+ w = 10; h = 10
130
+ body = `<path d='M0 0L10 10M-2 8L2 12M8 -2L12 2' stroke='${fg}' stroke-width='1.5'/>`
131
+ break
132
+ case 'cross-diag':
133
+ w = 10; h = 10
134
+ body = `<path d='M0 10L10 0M0 0L10 10' stroke='${fg}' stroke-width='1.2'/>`
135
+ break
136
+ case 'grid':
137
+ w = 12; h = 12
138
+ body = `<path d='M12 0H0V12' fill='none' stroke='${fg}' stroke-width='1.2'/>`
139
+ break
140
+ case 'horizontal':
141
+ w = 8; h = 8
142
+ body = `<line x1='0' y1='4' x2='8' y2='4' stroke='${fg}' stroke-width='1.5'/>`
143
+ break
144
+ case 'vertical':
145
+ w = 8; h = 8
146
+ body = `<line x1='4' y1='0' x2='4' y2='8' stroke='${fg}' stroke-width='1.5'/>`
147
+ break
148
+ case 'brick':
149
+ w = 16; h = 16
150
+ body = `<path d='M0 8h16M8 0v8M0 16v-8' fill='none' stroke='${fg}' stroke-width='1.2'/>`
151
+ break
152
+ case 'wave':
153
+ w = 16; h = 8
154
+ body = `<path d='M0 4 Q4 0, 8 4 T16 4' fill='none' stroke='${fg}' stroke-width='1.3'/>`
155
+ break
156
+ case 'checker':
157
+ w = 12; h = 12
158
+ body = `<rect width='6' height='6' fill='${fg}'/><rect x='6' y='6' width='6' height='6' fill='${fg}'/>`
159
+ break
160
+ case 'zigzag':
161
+ w = 12; h = 8
162
+ body = `<path d='M0 6L6 2L12 6' fill='none' stroke='${fg}' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'/>`
163
+ break
164
+ default:
165
+ w = 10; h = 10
166
+ body = `<path d='M0 10L10 0M-2 2L2 -2M8 12L12 8' stroke='${fg}' stroke-width='1.5'/>`
167
+ break
168
+ }
169
+
170
+ const svg = `<svg xmlns='http://www.w3.org/2000/svg' width='${w}' height='${h}' data-pattern-type='${type}' data-fg='${rawFg}' data-bg='${rawBg}'><rect width='${w}' height='${h}' fill='${bg}'/>${body}</svg>`
171
+ const encoded = svg
172
+ .replace(/'/g, '%22')
173
+ .replace(/</g, '%3C')
174
+ .replace(/>/g, '%3E')
175
+ .replace(/\s+/g, '%20')
176
+ return `data:image/svg+xml;utf8,${encoded}`
177
+ }
178
+
179
+ export function parsePatternSvgMeta(url?: string): { type?: string; fg?: string; bg?: string } {
180
+ if (!url || typeof url !== 'string' || !url.startsWith('data:image/svg+xml')) return {}
181
+ let decoded = url
182
+ try {
183
+ decoded = decodeURIComponent(url)
184
+ } catch {
185
+ decoded = url.replace(/%22/g, '"').replace(/%27/g, "'").replace(/%23/g, '#')
186
+ }
187
+ const typeMatch = decoded.match(/data-pattern-type=['"]([^'"]+)['"]/)
188
+ const fgMatch = decoded.match(/data-fg=['"]([^'"]+)['"]/)
189
+ const bgMatch = decoded.match(/data-bg=['"]([^'"]+)['"]/)
190
+ let type = typeMatch ? typeMatch[1] : undefined
191
+ if (!type) {
192
+ if (decoded.includes('M0 10L10 0M0 0L10 10')) type = 'cross-diag'
193
+ else if (decoded.includes('M0 10L10 0')) type = 'diag-up'
194
+ else if (decoded.includes('M12 0H0V12')) type = 'grid'
195
+ else if (decoded.includes('cx="5" cy="5" r="2"') || decoded.includes("cx='5' cy='5' r='2'")) type = 'dots-dense'
196
+ else if (decoded.includes('M0 8h16')) type = 'brick'
197
+ else if (decoded.includes('y1="4" x2="8"') || decoded.includes("y1='4' x2='8'")) type = 'horizontal'
198
+ else if (decoded.includes('Q4 0, 8 4')) type = 'wave'
199
+ else if (decoded.includes('width="6" height="6"') || decoded.includes("width='6' height='6'")) type = 'checker'
200
+ }
201
+ return {
202
+ type,
203
+ fg: fgMatch ? fgMatch[1].replace(/%23/g, '#') : undefined,
204
+ bg: bgMatch ? bgMatch[1].replace(/%23/g, '#') : undefined
205
+ }
206
+ }
207
+
73
208
  export const PATTERN_PRESETS = [
74
- { name: '斜线', url: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><rect width='10' height='10' fill='%23ffffff'/><path d='M0 10L10 0M-2 2L2 -2M8 12L12 8' stroke='%233b82f6' stroke-width='1.5'/></svg>" },
75
- { name: '网格', url: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12'><rect width='12' height='12' fill='%23ffffff'/><path d='M12 0H0V12' fill='none' stroke='%233b82f6' stroke-width='1.2'/></svg>" },
76
- { name: '圆点', url: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><rect width='10' height='10' fill='%23ffffff'/><circle cx='5' cy='5' r='2' fill='%233b82f6'/></svg>" },
77
- { name: '砖块', url: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16'><rect width='16' height='16' fill='%23ffffff'/><path d='M0 8h16M8 0v8M0 16v-8' fill='none' stroke='%233b82f6' stroke-width='1.2'/></svg>" },
78
- { name: '横线', url: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='8' height='8'><rect width='8' height='8' fill='%23ffffff'/><line x1='0' y1='4' x2='8' y2='4' stroke='%233b82f6' stroke-width='1.5'/></svg>" },
79
- { name: '波浪', url: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='8'><rect width='16' height='8' fill='%23ffffff'/><path d='M0 4 Q4 0, 8 4 T16 4' fill='none' stroke='%233b82f6' stroke-width='1.2'/></svg>" },
80
- { name: '棋盘格', url: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12'><rect width='12' height='12' fill='%23ffffff'/><rect width='6' height='6' fill='%233b82f6'/><rect x='6' y='6' width='6' height='6' fill='%233b82f6'/></svg>" },
81
- { name: '交叉线', url: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><rect width='10' height='10' fill='%23ffffff'/><path d='M0 10L10 0M0 0L10 10' stroke='%233b82f6' stroke-width='1.2'/></svg>" }
209
+ { key: 'dots-sparse', name: '稀疏圆点', url: buildPatternSvgDataUrl('dots-sparse', '#3B82F6', '#FFFFFF') },
210
+ { key: 'dots-dense', name: '密集圆点', url: buildPatternSvgDataUrl('dots-dense', '#3B82F6', '#FFFFFF') },
211
+ { key: 'diag-up', name: '上对角线', url: buildPatternSvgDataUrl('diag-up', '#3B82F6', '#FFFFFF') },
212
+ { key: 'diag-down', name: '下对角线', url: buildPatternSvgDataUrl('diag-down', '#3B82F6', '#FFFFFF') },
213
+ { key: 'cross-diag', name: '交叉斜线', url: buildPatternSvgDataUrl('cross-diag', '#3B82F6', '#FFFFFF') },
214
+ { key: 'grid', name: '细网格', url: buildPatternSvgDataUrl('grid', '#3B82F6', '#FFFFFF') },
215
+ { key: 'horizontal', name: '横线', url: buildPatternSvgDataUrl('horizontal', '#3B82F6', '#FFFFFF') },
216
+ { key: 'vertical', name: '竖线', url: buildPatternSvgDataUrl('vertical', '#3B82F6', '#FFFFFF') },
217
+ { key: 'brick', name: '砖块', url: buildPatternSvgDataUrl('brick', '#3B82F6', '#FFFFFF') },
218
+ { key: 'wave', name: '波浪', url: buildPatternSvgDataUrl('wave', '#3B82F6', '#FFFFFF') },
219
+ { key: 'checker', name: '棋盘格', url: buildPatternSvgDataUrl('checker', '#3B82F6', '#FFFFFF') },
220
+ { key: 'zigzag', name: '之字形', url: buildPatternSvgDataUrl('zigzag', '#3B82F6', '#FFFFFF') }
82
221
  ]
83
222
 
84
223
  export function useSidebarShapeFormat(
@@ -143,13 +282,17 @@ export function useSidebarShapeFormat(
143
282
 
144
283
  function hasElementFill(el: any): boolean {
145
284
  if (!el) return false
285
+ if (activeFillType.value && activeFillType.value !== 'transparent') {
286
+ return true
287
+ }
146
288
  // 1. 渐变填充
147
289
  const rawFill = (el.style && el.style.fill !== undefined) ? el.style.fill : el.fill
148
290
  if (el.gradient || el.style?.gradient || (rawFill && typeof rawFill === 'object' && rawFill.type === 'gradient')) {
149
291
  return true
150
292
  }
151
293
  // 2. 图片/图案填充
152
- if (el.pattern || el.style?.pattern || (rawFill && typeof rawFill === 'object' && (rawFill.type === 'image' || rawFill.type === 'pattern'))) {
294
+ const hasPatternVal = (el.pattern !== undefined && el.pattern !== '') || (el.style?.pattern !== undefined && el.style?.pattern !== '')
295
+ if (hasPatternVal || (rawFill && typeof rawFill === 'object' && (rawFill.type === 'image' || rawFill.type === 'pattern'))) {
153
296
  return true
154
297
  }
155
298
  // 3. 常规色彩填充
@@ -258,6 +401,9 @@ export function useSidebarShapeFormat(
258
401
 
259
402
  const isFillEnabled = computed({
260
403
  get() {
404
+ if (activeFillType.value && activeFillType.value !== 'transparent') {
405
+ return true
406
+ }
261
407
  if (isMultiOrGroup()) {
262
408
  const elements = getFormattingElements()
263
409
  return elements.some((el: any) => hasElementFill(el))
@@ -265,42 +411,20 @@ export function useSidebarShapeFormat(
265
411
  return hasElementFill(selectedElement.value)
266
412
  },
267
413
  set(val: boolean) {
414
+ if (!val) {
415
+ activeFillType.value = null
416
+ }
268
417
  const newType = val ? 'color' : 'transparent'
269
- if (isMultiOrGroup()) {
270
- const elements = getFormattingElements()
271
- elements.forEach((el: any) => {
272
- if (!el.style) el.style = {}
273
- if (newType === 'transparent') {
274
- el.style.fill = 'transparent'
275
- el.fill = 'transparent'
276
- delete el.gradient
277
- delete el.style.gradient
278
- delete el.pattern
279
- delete el.style.pattern
280
- } else {
281
- if (el.style.fill === 'transparent' || !el.style.fill || el.fill === 'transparent') {
282
- let defaultColor = '#3b82f6'
283
- if (el.type === 'shape') {
284
- const shapeType = el.content || 'rect'
285
- if (shapeType === 'circle') defaultColor = '#3b82f6'
286
- else if (shapeType === 'triangle' || shapeType === 'M 100 0 L 0 200 L 200 200 Z') defaultColor = '#f59e0b'
287
- else defaultColor = '#10b981'
288
- }
289
- el.style.fill = defaultColor
290
- el.fill = defaultColor
291
- }
292
- }
293
- })
294
- if (selectedElement.value?.type === 'group') {
295
- if (!selectedElement.value.style) selectedElement.value.style = {}
296
- selectedElement.value.style.fill = newType === 'transparent' ? 'transparent' : '#3b82f6'
297
- selectedElement.value.fill = newType === 'transparent' ? 'transparent' : '#3b82f6'
298
- }
299
- } else {
300
- if (!selectedElement.value) return
301
- const el = selectedElement.value
418
+ const elements = getFormattingElements()
419
+ elements.forEach((el: any) => {
302
420
  if (!el.style) el.style = {}
303
421
  if (newType === 'transparent') {
422
+ const curG = getStandardGradientConfig(el)
423
+ if (curG) el._lastGradient = curG
424
+ const curS = el.style.fill || el.fill
425
+ if (typeof curS === 'string' && curS !== 'none' && curS !== 'transparent') {
426
+ el._lastSolidFill = curS
427
+ }
304
428
  el.style.fill = 'transparent'
305
429
  el.fill = 'transparent'
306
430
  delete el.gradient
@@ -308,7 +432,14 @@ export function useSidebarShapeFormat(
308
432
  delete el.pattern
309
433
  delete el.style.pattern
310
434
  } else {
311
- if (el.style.fill === 'transparent' || !el.style.fill || el.fill === 'transparent') {
435
+ if (el._lastGradient) {
436
+ el.gradient = el._lastGradient
437
+ el.fill = { type: 'gradient', value: el._lastGradient }
438
+ el.style.fill = 'none'
439
+ } else if (el._lastSolidFill) {
440
+ el.style.fill = el._lastSolidFill
441
+ el.fill = el._lastSolidFill
442
+ } else if (el.style.fill === 'transparent' || !el.style.fill || el.fill === 'transparent') {
312
443
  let defaultColor = '#3b82f6'
313
444
  if (el.type === 'shape') {
314
445
  const shapeType = el.content || 'rect'
@@ -320,6 +451,11 @@ export function useSidebarShapeFormat(
320
451
  el.fill = defaultColor
321
452
  }
322
453
  }
454
+ })
455
+ if (selectedElement.value?.type === 'group') {
456
+ if (!selectedElement.value.style) selectedElement.value.style = {}
457
+ selectedElement.value.style.fill = newType === 'transparent' ? 'transparent' : (selectedElement.value._lastSolidFill || '#3b82f6')
458
+ selectedElement.value.fill = newType === 'transparent' ? 'transparent' : (selectedElement.value._lastSolidFill || '#3b82f6')
323
459
  }
324
460
  addHistorySnapshot()
325
461
  }
@@ -332,10 +468,11 @@ export function useSidebarShapeFormat(
332
468
  if (activeFillType.value) return activeFillType.value
333
469
 
334
470
  const rawFill = (el.style && el.style.fill !== undefined) ? el.style.fill : el.fill
471
+ const hasPattern = (el.pattern !== undefined && el.pattern !== '') || (el.style?.pattern !== undefined && el.style?.pattern !== '')
335
472
  const patternVal = el.pattern || el.style?.pattern || (rawFill && typeof rawFill === 'object' && (rawFill.type === 'image' || rawFill.type === 'pattern') ? (rawFill.value?.src || rawFill.value?.url || rawFill.value) : '')
336
473
 
337
- if (patternVal) {
338
- if (PATTERN_PRESETS.some(item => item.url === patternVal)) {
474
+ if (hasPattern || patternVal) {
475
+ if (patternVal && (String(patternVal).startsWith('data:image/svg+xml') || el.patternType || el.style?.patternType || PATTERN_PRESETS.some(item => item.url === patternVal))) {
339
476
  return 'pattern'
340
477
  }
341
478
  return 'image'
@@ -347,29 +484,108 @@ export function useSidebarShapeFormat(
347
484
  },
348
485
  set(val: string) {
349
486
  if (!selectedElement.value) return
350
- const el = selectedElement.value
487
+ const elements = getFormattingElements()
351
488
  activeFillType.value = val
352
- if (!el.style) el.style = {}
353
-
354
- delete el.fill
355
- delete el.style.fill
356
- delete el.gradient
357
- delete el.style.gradient
358
- delete el.pattern
359
- delete el.style.pattern
360
-
361
- if (val === 'solid') {
362
- el.style.fill = '#3b82f6'
363
- } else if (val === 'gradient') {
364
- updateGradientConfig({})
365
- } else if (val === 'image') {
366
- el.style.fill = 'none'
367
- } else if (val === 'pattern') {
368
- const defaultPat = PATTERN_PRESETS[0].url
369
- el.pattern = defaultPat
370
- el.style.pattern = defaultPat
371
- el.style.fill = 'none'
372
- }
489
+
490
+ elements.forEach((el: any) => {
491
+ if (!el.style) el.style = {}
492
+
493
+ if (val === 'solid') {
494
+ const curG = getStandardGradientConfig(el)
495
+ if (curG) {
496
+ el._lastGradient = curG
497
+ }
498
+ let targetSolid = el._lastSolidFill
499
+ if (!targetSolid || targetSolid === 'none' || targetSolid === 'transparent') {
500
+ if (curG?.colors && curG.colors.length > 0 && curG.colors[0]?.color) {
501
+ targetSolid = curG.colors[0].color
502
+ } else {
503
+ const current = el.style?.fill || el.fill
504
+ if (typeof current === 'string' && current !== 'none' && current !== 'transparent') {
505
+ targetSolid = current
506
+ } else if (fillHex.value && fillHex.value !== 'transparent' && fillHex.value !== 'none') {
507
+ targetSolid = fillHex.value
508
+ } else {
509
+ targetSolid = '#3b82f6'
510
+ }
511
+ }
512
+ }
513
+ el._lastSolidFill = targetSolid
514
+ delete el.gradient
515
+ delete el.style.gradient
516
+ delete el.pattern
517
+ delete el.style.pattern
518
+ el.style.fill = targetSolid
519
+ el.fill = targetSolid
520
+ styleFill.value = targetSolid
521
+ } else if (val === 'gradient') {
522
+ const current = el.style?.fill || el.fill
523
+ if (typeof current === 'string' && current !== 'none' && current !== 'transparent') {
524
+ el._lastSolidFill = current
525
+ } else if (fillHex.value && fillHex.value !== 'transparent' && fillHex.value !== 'none') {
526
+ el._lastSolidFill = fillHex.value
527
+ }
528
+ delete el.pattern
529
+ delete el.style.pattern
530
+
531
+ const cachedG = getStandardGradientConfig(el) || el._lastGradient
532
+ if (cachedG && cachedG.colors && cachedG.colors.length >= 2) {
533
+ updateGradientConfig({
534
+ colors: cachedG.colors,
535
+ path: cachedG.path,
536
+ rot: cachedG.rot,
537
+ direction: cachedG.direction,
538
+ directionName: cachedG.directionName,
539
+ center: cachedG.center,
540
+ fillToRect: cachedG.fillToRect
541
+ })
542
+ } else {
543
+ const startColor = el._lastSolidFill ? parseColor(el._lastSolidFill).hex : (fillHex.value && fillHex.value !== 'transparent' && fillHex.value !== 'none' ? fillHex.value : '#3b82f6')
544
+ const endColor = generateGradientEndColor(startColor)
545
+ updateGradientConfig({
546
+ startColor,
547
+ endColor
548
+ })
549
+ }
550
+ } else if (val === 'image') {
551
+ const curG = getStandardGradientConfig(el)
552
+ if (curG) el._lastGradient = curG
553
+ const curS = el.style?.fill || el.fill
554
+ if (typeof curS === 'string' && curS !== 'none' && curS !== 'transparent') {
555
+ el._lastSolidFill = curS
556
+ }
557
+ delete el.gradient
558
+ delete el.style.gradient
559
+ const existing = el.pattern || el.style?.pattern || ''
560
+ const isPreset = String(existing).startsWith('data:image/svg+xml') || PATTERN_PRESETS.some(item => item.url === existing)
561
+ const patVal = (!existing || isPreset) ? '' : existing
562
+ el.pattern = patVal
563
+ el.style.pattern = patVal
564
+ el.style.fill = 'none'
565
+ } else if (val === 'pattern') {
566
+ const curG = getStandardGradientConfig(el)
567
+ if (curG) el._lastGradient = curG
568
+ const curS = el.style?.fill || el.fill
569
+ if (typeof curS === 'string' && curS !== 'none' && curS !== 'transparent') {
570
+ el._lastSolidFill = curS
571
+ }
572
+ delete el.gradient
573
+ delete el.style.gradient
574
+ const pType = el.patternType || el.style?.patternType || 'diag-up'
575
+ const pFg = el.patternFg || el.style?.patternFg || (el._lastSolidFill && el._lastSolidFill !== 'none' && el._lastSolidFill !== 'transparent' ? el._lastSolidFill : '#3B82F6')
576
+ const pBg = el.patternBg || el.style?.patternBg || '#FFFFFF'
577
+ el.patternType = pType
578
+ el.patternFg = pFg
579
+ el.patternBg = pBg
580
+ el.style.patternType = pType
581
+ el.style.patternFg = pFg
582
+ el.style.patternBg = pBg
583
+ const patUrl = buildPatternSvgDataUrl(pType, pFg, pBg)
584
+ el.pattern = patUrl
585
+ el.style.pattern = patUrl
586
+ el.style.fill = 'none'
587
+ }
588
+ })
373
589
  addHistorySnapshot()
374
590
  }
375
591
  })
@@ -386,26 +602,78 @@ export function useSidebarShapeFormat(
386
602
  }
387
603
  return 'transparent'
388
604
  }
605
+ const el = selectedElement.value
606
+ if (el?.style?.fill && el.style.fill !== 'none' && el.style.fill !== 'transparent') {
607
+ return parseColor(el.style.fill).hex
608
+ }
609
+ if (el?.fill && typeof el.fill === 'string' && el.fill !== 'none' && el.fill !== 'transparent') {
610
+ return parseColor(el.fill).hex
611
+ }
612
+ if (el?._lastSolidFill) {
613
+ return parseColor(el._lastSolidFill).hex
614
+ }
389
615
  const fill = styleFill.value || '#363B4C'
390
616
  return parseColor(fill).hex
391
617
  },
392
618
  set(val: string) {
393
619
  if (isMultiOrGroup()) {
394
620
  const elements = getFormattingElements()
621
+ const parsed = parseColor(val)
395
622
  elements.forEach((el: any) => {
396
623
  if (!el.style) el.style = {}
397
- const currentFill = el.style.fill || '#363B4C'
398
- const currentAlpha = parseColor(currentFill).alpha
399
- el.style.fill = hexToRgba(val, currentAlpha)
624
+ delete el.gradient
625
+ delete el.style.gradient
626
+ delete el.pattern
627
+ delete el.style.pattern
628
+ if (val === 'transparent' || val === 'none') {
629
+ el.style.fill = 'transparent'
630
+ el.fill = 'transparent'
631
+ } else {
632
+ const currentFill = el.style.fill || '#363B4C'
633
+ let currentAlpha = parseColor(currentFill).alpha
634
+ if (currentAlpha === 0 || currentAlpha === undefined) currentAlpha = 100
635
+ const alphaToUse = (parsed.alpha !== undefined && parsed.alpha < 100) ? parsed.alpha : currentAlpha
636
+ const rgba = hexToRgba(parsed.hex, alphaToUse)
637
+ el.style.fill = rgba
638
+ el.fill = rgba
639
+ el._lastSolidFill = rgba
640
+ }
400
641
  })
401
642
  if (selectedElement.value?.type === 'group') {
402
643
  if (!selectedElement.value.style) selectedElement.value.style = {}
403
644
  selectedElement.value.style.fill = val
645
+ selectedElement.value.fill = val
646
+ selectedElement.value._lastSolidFill = val
404
647
  }
405
648
  } else {
406
649
  if (!selectedElement.value) return
407
- const alpha = fillAlpha.value
408
- styleFill.value = hexToRgba(val, alpha)
650
+ const el = selectedElement.value
651
+ delete el.gradient
652
+ if (el.style) delete el.style.gradient
653
+ delete el.pattern
654
+ if (el.style) delete el.style.pattern
655
+ activeFillType.value = 'solid'
656
+ if (val === 'transparent' || val === 'none') {
657
+ styleFill.value = 'transparent'
658
+ el.fill = 'transparent'
659
+ if (el.style) el.style.fill = 'transparent'
660
+ } else {
661
+ const parsed = parseColor(val)
662
+ let alpha = fillAlpha.value
663
+ if (alpha === 0 || alpha === undefined) alpha = 100
664
+ if (parsed.alpha !== undefined && parsed.alpha < 100) {
665
+ alpha = parsed.alpha
666
+ }
667
+ const rgba = hexToRgba(parsed.hex, alpha)
668
+ styleFill.value = rgba
669
+ el.fill = rgba
670
+ if (el.style) el.style.fill = rgba
671
+ el._lastSolidFill = rgba
672
+ if (parsed.alpha !== undefined && parsed.alpha < 100) {
673
+ el.opacity = alpha / 100
674
+ if (el.style) el.style.opacity = alpha / 100
675
+ }
676
+ }
409
677
  }
410
678
  addHistorySnapshot()
411
679
  }
@@ -415,6 +683,13 @@ export function useSidebarShapeFormat(
415
683
  get() {
416
684
  if (selectedElement.value) {
417
685
  const el = selectedElement.value
686
+ const rawFill = (el.style && el.style.fill !== undefined) ? el.style.fill : el.fill
687
+ if (rawFill) {
688
+ const colorParsed = parseColor(rawFill)
689
+ if (colorParsed.alpha !== undefined && colorParsed.alpha < 100) {
690
+ return colorParsed.alpha
691
+ }
692
+ }
418
693
  const op = el.style?.opacity !== undefined ? el.style.opacity : (el.opacity !== undefined ? el.opacity : undefined)
419
694
  if (op !== undefined) {
420
695
  return Math.round((typeof op === 'number' ? op : parseFloat(op) || 1) * 100)
@@ -433,7 +708,10 @@ export function useSidebarShapeFormat(
433
708
  el.style.opacity = alpha
434
709
  const currentFill = el.style.fill || '#363B4C'
435
710
  const hex = parseColor(currentFill).hex
436
- el.style.fill = hexToRgba(hex, val)
711
+ const validHex = (hex && hex !== 'transparent') ? hex : '#3b82f6'
712
+ const rgba = hexToRgba(validHex, val)
713
+ el.style.fill = rgba
714
+ el.fill = rgba
437
715
  })
438
716
  if (selectedElement.value) {
439
717
  if (!selectedElement.value.style) selectedElement.value.style = {}
@@ -447,8 +725,11 @@ export function useSidebarShapeFormat(
447
725
  const alpha = val / 100
448
726
  el.opacity = alpha
449
727
  el.style.opacity = alpha
450
- const hex = fillHex.value
451
- styleFill.value = hexToRgba(hex, val)
728
+ const hex = (fillHex.value && fillHex.value !== 'transparent') ? fillHex.value : '#3b82f6'
729
+ const rgba = hexToRgba(hex, val)
730
+ styleFill.value = rgba
731
+ el.fill = rgba
732
+ if (el.style) el.style.fill = rgba
452
733
  }
453
734
  addHistorySnapshot()
454
735
  }
@@ -462,8 +743,16 @@ export function useSidebarShapeFormat(
462
743
  const gradientStartHex = computed<string>({
463
744
  get() {
464
745
  if (!selectedElement.value) return '#3b82f6'
465
- const g = getStandardGradientConfig(selectedElement.value)
466
- return g?.colors?.[0]?.color || '#3b82f6'
746
+ const g = getStandardGradientConfig(selectedElement.value) || selectedElement.value._lastGradient
747
+ if (g?.colors?.[0]?.color) {
748
+ return parseColor(g.colors[0].color).hex
749
+ }
750
+ const solid = selectedElement.value._lastSolidFill || styleFill.value || selectedElement.value.fill
751
+ if (solid && solid !== 'none' && solid !== 'transparent') {
752
+ const parsed = parseColor(solid)
753
+ if (parsed.hex && parsed.hex !== 'transparent') return parsed.hex
754
+ }
755
+ return '#3b82f6'
467
756
  },
468
757
  set(val: string) {
469
758
  updateGradientConfig({ startColor: val })
@@ -473,9 +762,13 @@ export function useSidebarShapeFormat(
473
762
  const gradientEndHex = computed<string>({
474
763
  get() {
475
764
  if (!selectedElement.value) return '#60a5fa'
476
- const g = getStandardGradientConfig(selectedElement.value)
765
+ const g = getStandardGradientConfig(selectedElement.value) || selectedElement.value._lastGradient
477
766
  const colors = g?.colors || []
478
- return colors[colors.length - 1]?.color || colors[0]?.color || '#60a5fa'
767
+ if (colors.length > 0 && colors[colors.length - 1]?.color) {
768
+ return parseColor(colors[colors.length - 1].color).hex
769
+ }
770
+ const start = gradientStartHex.value
771
+ return generateGradientEndColor(start)
479
772
  },
480
773
  set(val: string) {
481
774
  updateGradientConfig({ endColor: val })
@@ -542,12 +835,13 @@ export function useSidebarShapeFormat(
542
835
  directionName?: string
543
836
  center?: { x: number; y: number }
544
837
  fillToRect?: { l: number; t: number; r: number; b: number }
838
+ colors?: any[]
545
839
  }) {
546
840
  if (!selectedElement.value) return
547
841
  const el = selectedElement.value
548
- const existingG = getStandardGradientConfig(el)
842
+ const existingG = getStandardGradientConfig(el) || el._lastGradient
549
843
  const curStart = opt.startColor !== undefined ? opt.startColor : gradientStartHex.value
550
- const curEnd = opt.endColor !== undefined ? opt.endColor : gradientEndHex.value
844
+ const curEnd = opt.endColor !== undefined ? opt.endColor : (opt.startColor !== undefined ? generateGradientEndColor(opt.startColor) : gradientEndHex.value)
551
845
  const curPath = opt.path !== undefined ? opt.path : gradientPathType.value
552
846
  const curRot = opt.rot !== undefined ? opt.rot : (existingG?.rot !== undefined ? existingG.rot : gradientAngle.value)
553
847
 
@@ -575,10 +869,29 @@ export function useSidebarShapeFormat(
575
869
  curDirName = '沿路径'
576
870
  }
577
871
 
578
- const colors = [
579
- { color: curStart, pos: '0%' },
580
- { color: curEnd, pos: '100%' }
581
- ]
872
+ let colors: any[] = []
873
+ if (opt.colors && Array.isArray(opt.colors) && opt.colors.length > 0) {
874
+ colors = opt.colors
875
+ } else if (existingG?.colors && Array.isArray(existingG.colors) && existingG.colors.length >= 2) {
876
+ if (opt.startColor !== undefined || opt.endColor !== undefined) {
877
+ colors = existingG.colors.map((c: any, idx: number) => {
878
+ if (idx === 0 && opt.startColor !== undefined) {
879
+ return { ...c, color: opt.startColor }
880
+ }
881
+ if (idx === existingG.colors.length - 1 && opt.endColor !== undefined) {
882
+ return { ...c, color: opt.endColor }
883
+ }
884
+ return { ...c }
885
+ })
886
+ } else {
887
+ colors = existingG.colors
888
+ }
889
+ } else {
890
+ colors = [
891
+ { color: curStart, pos: '0%' },
892
+ { color: curEnd, pos: '100%' }
893
+ ]
894
+ }
582
895
 
583
896
  const normalized = normalizeGradientConfig({
584
897
  type: curPath === 'line' ? 'linear' : (curPath === 'circle' ? 'radial' : (curPath === 'rect' ? 'rect' : 'path')),
@@ -593,18 +906,21 @@ export function useSidebarShapeFormat(
593
906
  colors: colors
594
907
  })
595
908
 
596
- el.fill = {
597
- type: 'gradient',
598
- value: normalized
599
- }
600
-
601
- el.gradient = normalized
909
+ const elements = getFormattingElements()
910
+ elements.forEach((targetEl: any) => {
911
+ targetEl.fill = {
912
+ type: 'gradient',
913
+ value: normalized
914
+ }
915
+ targetEl.gradient = normalized
916
+ targetEl._lastGradient = normalized
602
917
 
603
- if (el.style) {
604
- delete el.style.pattern
605
- el.style.fill = 'none'
606
- }
607
- delete el.pattern
918
+ if (targetEl.style) {
919
+ delete targetEl.style.pattern
920
+ targetEl.style.fill = 'none'
921
+ }
922
+ delete targetEl.pattern
923
+ })
608
924
  addHistorySnapshot()
609
925
  }
610
926
 
@@ -632,6 +948,7 @@ export function useSidebarShapeFormat(
632
948
  value: normalized
633
949
  }
634
950
  targetEl.gradient = normalized
951
+ targetEl._lastGradient = normalized
635
952
 
636
953
  if (targetEl.style) {
637
954
  delete targetEl.style.pattern
@@ -646,9 +963,125 @@ export function useSidebarShapeFormat(
646
963
  }
647
964
  }
648
965
 
649
- const shapePatternFill = computed(() => {
650
- if (!selectedElement.value) return ''
651
- return selectedElement.value.pattern || selectedElement.value.style?.pattern || ''
966
+ const shapePatternFill = computed({
967
+ get() {
968
+ if (!selectedElement.value) return ''
969
+ return selectedElement.value.pattern || selectedElement.value.style?.pattern || ''
970
+ },
971
+ set(val: string) {
972
+ if (!selectedElement.value) return
973
+ const elements = getFormattingElements()
974
+ const meta = parsePatternSvgMeta(val)
975
+ elements.forEach((el: any) => {
976
+ if (!el.style) el.style = {}
977
+ if (meta.type) {
978
+ el.patternType = meta.type
979
+ el.style.patternType = meta.type
980
+ }
981
+ if (meta.fg) {
982
+ el.patternFg = meta.fg
983
+ el.style.patternFg = meta.fg
984
+ }
985
+ if (meta.bg) {
986
+ el.patternBg = meta.bg
987
+ el.style.patternBg = meta.bg
988
+ }
989
+ el.pattern = val
990
+ el.style.pattern = val
991
+ el.style.fill = 'none'
992
+ })
993
+ addHistorySnapshot()
994
+ }
995
+ })
996
+
997
+ const shapePatternType = computed({
998
+ get() {
999
+ if (!selectedElement.value) return 'diag-up'
1000
+ const el = selectedElement.value
1001
+ if (el.patternType || el.style?.patternType) return el.patternType || el.style.patternType
1002
+ const meta = parsePatternSvgMeta(el.pattern || el.style?.pattern)
1003
+ return meta.type || 'diag-up'
1004
+ },
1005
+ set(val: string) {
1006
+ if (!selectedElement.value) return
1007
+ const elements = getFormattingElements()
1008
+ const fg = shapePatternFg.value || '#3B82F6'
1009
+ const bg = shapePatternBg.value || '#FFFFFF'
1010
+ const url = buildPatternSvgDataUrl(val, fg, bg)
1011
+ elements.forEach((el: any) => {
1012
+ if (!el.style) el.style = {}
1013
+ el.patternType = val
1014
+ el.style.patternType = val
1015
+ el.patternFg = fg
1016
+ el.style.patternFg = fg
1017
+ el.patternBg = bg
1018
+ el.style.patternBg = bg
1019
+ el.pattern = url
1020
+ el.style.pattern = url
1021
+ el.style.fill = 'none'
1022
+ })
1023
+ addHistorySnapshot()
1024
+ }
1025
+ })
1026
+
1027
+ const shapePatternFg = computed({
1028
+ get() {
1029
+ if (!selectedElement.value) return '#3B82F6'
1030
+ const el = selectedElement.value
1031
+ if (el.patternFg || el.style?.patternFg) return el.patternFg || el.style.patternFg
1032
+ const meta = parsePatternSvgMeta(el.pattern || el.style?.pattern)
1033
+ return meta.fg || '#3B82F6'
1034
+ },
1035
+ set(val: string) {
1036
+ if (!selectedElement.value) return
1037
+ const elements = getFormattingElements()
1038
+ const pType = shapePatternType.value || 'diag-up'
1039
+ const bg = shapePatternBg.value || '#FFFFFF'
1040
+ const url = buildPatternSvgDataUrl(pType, val, bg)
1041
+ elements.forEach((el: any) => {
1042
+ if (!el.style) el.style = {}
1043
+ el.patternType = pType
1044
+ el.style.patternType = pType
1045
+ el.patternFg = val
1046
+ el.style.patternFg = val
1047
+ el.patternBg = bg
1048
+ el.style.patternBg = bg
1049
+ el.pattern = url
1050
+ el.style.pattern = url
1051
+ el.style.fill = 'none'
1052
+ })
1053
+ addHistorySnapshot()
1054
+ }
1055
+ })
1056
+
1057
+ const shapePatternBg = computed({
1058
+ get() {
1059
+ if (!selectedElement.value) return '#FFFFFF'
1060
+ const el = selectedElement.value
1061
+ if (el.patternBg || el.style?.patternBg) return el.patternBg || el.style.patternBg
1062
+ const meta = parsePatternSvgMeta(el.pattern || el.style?.pattern)
1063
+ return meta.bg || '#FFFFFF'
1064
+ },
1065
+ set(val: string) {
1066
+ if (!selectedElement.value) return
1067
+ const elements = getFormattingElements()
1068
+ const pType = shapePatternType.value || 'diag-up'
1069
+ const fg = shapePatternFg.value || '#3B82F6'
1070
+ const url = buildPatternSvgDataUrl(pType, fg, val)
1071
+ elements.forEach((el: any) => {
1072
+ if (!el.style) el.style = {}
1073
+ el.patternType = pType
1074
+ el.style.patternType = pType
1075
+ el.patternFg = fg
1076
+ el.style.patternFg = fg
1077
+ el.patternBg = val
1078
+ el.style.patternBg = val
1079
+ el.pattern = url
1080
+ el.style.pattern = url
1081
+ el.style.fill = 'none'
1082
+ })
1083
+ addHistorySnapshot()
1084
+ }
652
1085
  })
653
1086
 
654
1087
  function handleShapePatternUpload(e: Event) {
@@ -661,6 +1094,7 @@ export function useSidebarShapeFormat(
661
1094
  if (!selectedElement.value.style) selectedElement.value.style = {}
662
1095
  selectedElement.value.pattern = dataUrl
663
1096
  selectedElement.value.style.pattern = dataUrl
1097
+ selectedElement.value.style.fill = 'none'
664
1098
  addHistorySnapshot()
665
1099
  notify('已设置形状图片填充')
666
1100
  }
@@ -670,9 +1104,10 @@ export function useSidebarShapeFormat(
670
1104
 
671
1105
  function clearShapePatternFill() {
672
1106
  if (selectedElement.value) {
673
- delete selectedElement.value.pattern
1107
+ selectedElement.value.pattern = ''
674
1108
  if (selectedElement.value.style) {
675
- delete selectedElement.value.style.pattern
1109
+ selectedElement.value.style.pattern = ''
1110
+ selectedElement.value.style.fill = 'none'
676
1111
  }
677
1112
  addHistorySnapshot()
678
1113
  notify('已清除图片填充')
@@ -802,11 +1237,18 @@ export function useSidebarShapeFormat(
802
1237
  set(val: string) {
803
1238
  if (isMultiOrGroup()) {
804
1239
  const elements = getFormattingElements().filter((el: any) => el.type !== 'line')
1240
+ const parsed = parseColor(val)
805
1241
  elements.forEach((el: any) => {
806
1242
  if (!el.style) el.style = {}
807
- const currentBorder = el.style.borderColor || '#363B4C'
808
- const currentAlpha = parseColor(currentBorder).alpha
809
- el.style.borderColor = hexToRgba(val, currentAlpha)
1243
+ if (val === 'transparent' || val === 'none') {
1244
+ el.style.borderColor = 'transparent'
1245
+ } else {
1246
+ const currentBorder = el.style.borderColor || '#363B4C'
1247
+ let currentAlpha = parseColor(currentBorder).alpha
1248
+ if (currentAlpha === 0 || currentAlpha === undefined) currentAlpha = 100
1249
+ const alphaToUse = (parsed.alpha !== undefined && parsed.alpha < 100) ? parsed.alpha : currentAlpha
1250
+ el.style.borderColor = hexToRgba(parsed.hex, alphaToUse)
1251
+ }
810
1252
  })
811
1253
  if (selectedElement.value?.type === 'group') {
812
1254
  if (!selectedElement.value.style) selectedElement.value.style = {}
@@ -814,8 +1256,17 @@ export function useSidebarShapeFormat(
814
1256
  }
815
1257
  } else {
816
1258
  if (!selectedElement.value) return
817
- const alpha = borderAlpha.value
818
- getStyle().borderColor = hexToRgba(val, alpha)
1259
+ if (val === 'transparent' || val === 'none') {
1260
+ getStyle().borderColor = 'transparent'
1261
+ } else {
1262
+ const parsed = parseColor(val)
1263
+ let alpha = borderAlpha.value
1264
+ if (alpha === 0 || alpha === undefined) alpha = 100
1265
+ if (parsed.alpha !== undefined && parsed.alpha < 100) {
1266
+ alpha = parsed.alpha
1267
+ }
1268
+ getStyle().borderColor = hexToRgba(parsed.hex, alpha)
1269
+ }
819
1270
  }
820
1271
  addHistorySnapshot()
821
1272
  }
@@ -828,8 +1279,9 @@ export function useSidebarShapeFormat(
828
1279
  },
829
1280
  set(val: number) {
830
1281
  if (!selectedElement.value) return
831
- const hex = borderHex.value
1282
+ const hex = (borderHex.value && borderHex.value !== 'transparent') ? borderHex.value : '#363B4C'
832
1283
  getStyle().borderColor = hexToRgba(hex, val)
1284
+ addHistorySnapshot()
833
1285
  }
834
1286
  })
835
1287
 
@@ -1305,6 +1757,9 @@ export function useSidebarShapeFormat(
1305
1757
  updateGradientConfig,
1306
1758
  updateElementGradient,
1307
1759
  shapePatternFill,
1760
+ shapePatternType,
1761
+ shapePatternFg,
1762
+ shapePatternBg,
1308
1763
  handleShapePatternUpload,
1309
1764
  clearShapePatternFill,
1310
1765
  styleBorderStyle,