jvs-ppt-ui 0.0.8 → 0.0.9

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jvs-ppt-ui",
3
3
  "private": false,
4
- "version": "0.0.8",
4
+ "version": "0.0.9",
5
5
  "type": "module",
6
6
  "main": "./dist/lib/jvs-ppt-ui.umd.js",
7
7
  "module": "./dist/lib/jvs-ppt-ui.es.js",
@@ -3086,6 +3086,9 @@ function stopCreationDrag() {
3086
3086
  }
3087
3087
 
3088
3088
  activeSlide.value.elements.push(createdElement)
3089
+ if (createdElement.type === 'line' && (createdElement.startConnection || createdElement.endConnection)) {
3090
+ syncSlideConnections(activeSlide.value, logicalWidth.value, logicalHeight.value, [createdElement.id])
3091
+ }
3089
3092
  selectedElementId.value = createdElement.id
3090
3093
  selectedElementIds.value = [createdElement.id]
3091
3094
  justCreatedElement = true
@@ -4575,14 +4578,37 @@ function startDrag(e: MouseEvent, el: any) {
4575
4578
  : selectedEls
4576
4579
 
4577
4580
  // 检查是否有连线需要同步,避免无连线时每帧循环
4578
- const selIds = new Set(selectedElementIds.value)
4579
- hasConnectionsToSync = (activeSlide.value?.elements || []).some((it: any) =>
4580
- it && it.type === 'line' && (
4581
- selIds.has(it.id) ||
4582
- (it.startConnection && selIds.has(it.startConnection.id)) ||
4583
- (it.endConnection && selIds.has(it.endConnection.id))
4584
- )
4585
- )
4581
+ const selIds = new Set<string>(selectedElementIds.value)
4582
+ if (Array.isArray(targetIds)) targetIds.forEach(id => selIds.add(id))
4583
+ dragTargetEls.forEach((item: any) => {
4584
+ if (item?.id) selIds.add(item.id)
4585
+ if (item?.type === 'group' && Array.isArray(item.elements)) {
4586
+ const addChildren = (children: any[]) => {
4587
+ children.forEach(ch => {
4588
+ if (ch?.id) selIds.add(ch.id)
4589
+ if (ch?.type === 'group' && Array.isArray(ch.elements)) addChildren(ch.elements)
4590
+ })
4591
+ }
4592
+ addChildren(item.elements)
4593
+ }
4594
+ })
4595
+ const checkHasConnection = (elements: any[]): boolean => {
4596
+ for (const it of elements) {
4597
+ if (!it) continue
4598
+ if (it.type === 'line' && (
4599
+ selIds.has(it.id) ||
4600
+ (it.startConnection && selIds.has(it.startConnection.id)) ||
4601
+ (it.endConnection && selIds.has(it.endConnection.id))
4602
+ )) {
4603
+ return true
4604
+ }
4605
+ if (it.type === 'group' && Array.isArray(it.elements)) {
4606
+ if (checkHasConnection(it.elements)) return true
4607
+ }
4608
+ }
4609
+ return false
4610
+ }
4611
+ hasConnectionsToSync = checkHasConnection(activeSlide.value?.elements || [])
4586
4612
 
4587
4613
  // Record drag states
4588
4614
  dragStates = dragTargetEls.map((item: any) => {
@@ -7920,8 +7946,12 @@ async function handleGlobalPaste(e: ClipboardEvent) {
7920
7946
  v-show="el.visible !== false"
7921
7947
  v-memo="[
7922
7948
  (isDragging || isCanvasDragging)
7923
- ? (selectedElementIds.includes(el.id) ? `${el.style?.left}_${el.style?.top}_${el.style?.width}_${el.style?.height}_${el.style?.rotate}` : 'frozen')
7924
- : `${el.id}_${el.style?.left}_${el.style?.top}_${el.style?.width}_${el.style?.height}_${el.style?.rotate}_${selectedElementIds.includes(el.id)}_${selectedElementId === el.id}_${isEditingText}_${activeTab}_${activeSlideVersion}`
7949
+ ? (selectedElementIds.includes(el.id)
7950
+ ? `${el.style?.left}_${el.style?.top}_${el.style?.width}_${el.style?.height}_${el.style?.rotate}`
7951
+ : (el.type === 'line'
7952
+ ? `${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(',')}`
7953
+ : 'frozen'))
7954
+ : `${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}`
7925
7955
  ]"
7926
7956
  :class="{
7927
7957
  selected: selectedElementIds.includes(el.id),
@@ -7189,12 +7189,16 @@ const importPptxArrayBuffer = async (buffer: ArrayBuffer, fileName?: string): Pr
7189
7189
  reader.readAsArrayBuffer(file)
7190
7190
  }
7191
7191
 
7192
- const props = defineProps<{
7192
+ const props = withDefaults(defineProps<{
7193
7193
  slotProps?: any
7194
- }>()
7194
+ showMenu?: boolean
7195
+ }>(), {
7196
+ showMenu: true
7197
+ })
7195
7198
 
7196
7199
  const headerContext = computed(() => ({
7197
7200
  ...(props.slotProps || {}),
7201
+ showMenu: props.showMenu,
7198
7202
  presentationTitle: presentationTitle.value,
7199
7203
  isEditingTitle: isEditingTitle.value,
7200
7204
  startEditingTitle,
@@ -7232,7 +7236,7 @@ const importPptxArrayBuffer = async (buffer: ArrayBuffer, fileName?: string): Pr
7232
7236
 
7233
7237
  <!-- 外部左侧主体插槽 (默认呈现内置菜单和标题) -->
7234
7238
  <slot name="left" v-bind="headerContext">
7235
- <el-popover ref="menuPopoverRef" placement="bottom-start" :width="320" trigger="click"
7239
+ <el-popover v-if="showMenu" ref="menuPopoverRef" placement="bottom-start" :width="320" trigger="click"
7236
7240
  popper-style="padding: 0; border-radius: 12px; box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1); border: 1px solid #e2e8f0; background: #fff; overflow: hidden;">
7237
7241
  <template #reference>
7238
7242
  <button class="btn-icon" style="padding: 4px; color: var(--text-primary); flex-shrink: 0;" data-tooltip="菜单">
@@ -7401,7 +7405,7 @@ const importPptxArrayBuffer = async (buffer: ArrayBuffer, fileName?: string): Pr
7401
7405
  </div>
7402
7406
 
7403
7407
  <!-- Center Section: Integrated Top Toolbar -->
7404
- <div class="header-center-tools" :style="{ justifyContent: isMasterEditingView ? 'flex-start' : 'center' }" style="flex: 1; display: flex; align-items: center; overflow: hidden; min-width: 0; position: relative; z-index: 10; margin: 0 8px;">
7408
+ <div class="header-center-tools" :style="{ justifyContent: isMasterEditingView ? 'flex-start' : 'center' }" style="flex: 1; display: flex; align-items: center; overflow-x: auto; overflow-y: hidden; min-width: 0; position: relative; z-index: 10; margin: 0 8px; scrollbar-width: none; -ms-overflow-style: none;">
7405
7409
  <!-- 外部中间工具栏插槽 (默认呈现内置 Toolbar,支持前置/后置插槽扩展或整栏替换) -->
7406
7410
  <slot name="center" v-bind="headerContext">
7407
7411
  <slot name="toolbar" v-bind="headerContext">
@@ -7826,4 +7830,8 @@ const importPptxArrayBuffer = async (buffer: ArrayBuffer, fileName?: string): Pr
7826
7830
  font-weight: 500;
7827
7831
  font-family: inherit;
7828
7832
  }
7833
+
7834
+ .header-center-tools::-webkit-scrollbar {
7835
+ display: none;
7836
+ }
7829
7837
  </style>
@@ -33,6 +33,7 @@ const props = withDefaults(defineProps<PptEditorProps>(), {
33
33
  modelValue: null,
34
34
  readonly: false,
35
35
  showHeader: true,
36
+ showMenu: true,
36
37
  showSidebarLeft: true,
37
38
  showSidebarRight: true,
38
39
  enableMcp: false,
@@ -895,6 +896,12 @@ const headerSlotContext = computed(() => ({
895
896
  // 对外通过 ref 暴露所有方法
896
897
  defineExpose(editorApi)
897
898
 
899
+ const computedShowMenu = computed(() => {
900
+ if (props.showMenu !== undefined) return props.showMenu
901
+ if (props.config?.showMenu !== undefined) return props.config.showMenu
902
+ return true
903
+ })
904
+
898
905
  onMounted(async () => {
899
906
  // 触发 ready 事件
900
907
  emit('ready', editorApi)
@@ -928,7 +935,7 @@ onMounted(async () => {
928
935
  <div class="app-container jvs-ppt-editor-root" :class="{ 'is-readonly': readonly }">
929
936
  <!-- Main top Navigation bar (支持完整 header 插槽替换或精细区域插槽定制) -->
930
937
  <slot name="header" v-bind="headerSlotContext">
931
- <Header ref="headerRef" v-show="showHeader" :slot-props="headerSlotContext">
938
+ <Header ref="headerRef" v-show="showHeader" :show-menu="computedShowMenu" :slot-props="headerSlotContext">
932
939
  <!-- 左侧前置 (Logo、返回按钮等) -->
933
940
  <template v-if="slots['header-left-prefix']" #left-prefix="sp">
934
941
  <slot name="header-left-prefix" v-bind="sp"></slot>
@@ -1301,7 +1301,8 @@ function cancelInsertMedia() {
1301
1301
  .toolbar-ops-group {
1302
1302
  display: flex;
1303
1303
  align-items: center;
1304
- gap: 26px;
1304
+ gap: clamp(10px, 1.6vw, 24px);
1305
+ flex-shrink: 0;
1305
1306
  }
1306
1307
 
1307
1308
  .toolbar-v-divider {
@@ -1,5 +1,6 @@
1
1
  import { ref, nextTick, type Ref } from 'vue'
2
2
  import { SHAPE_PATH_FORMULAS } from '../../utils/pptxShapes'
3
+ import { syncSlideConnections } from '../../utils/line'
3
4
 
4
5
  export interface ResizeState {
5
6
  el: any
@@ -412,6 +413,9 @@ export function useCanvasTransform(options: CanvasTransformOptions) {
412
413
  }
413
414
  }
414
415
  }
416
+ if (activeSlide.value && activeSlide.value.elements) {
417
+ syncSlideConnections(activeSlide.value, logicalWidth.value, logicalHeight.value, selectedElementIds.value)
418
+ }
415
419
  }
416
420
 
417
421
  function stopResize() {
@@ -539,6 +543,9 @@ export function useCanvasTransform(options: CanvasTransformOptions) {
539
543
  newAngle = (newAngle % 360 + 360) % 360
540
544
  state.el.style.rotate = Math.round(newAngle)
541
545
  }
546
+ if (activeSlide.value && activeSlide.value.elements) {
547
+ syncSlideConnections(activeSlide.value, logicalWidth.value, logicalHeight.value, selectedElementIds.value)
548
+ }
542
549
  }
543
550
 
544
551
  function stopRotate() {
@@ -1031,6 +1038,9 @@ export function useCanvasTransform(options: CanvasTransformOptions) {
1031
1038
  parentGroup.elements[idx] = { ...curEl, path: newPath, content: newPath }
1032
1039
  }
1033
1040
  }
1041
+ if (activeSlide.value && activeSlide.value.elements) {
1042
+ syncSlideConnections(activeSlide.value, logicalWidth.value, logicalHeight.value, selectedElementIds.value)
1043
+ }
1034
1044
  }
1035
1045
 
1036
1046
  function stopDragKeypoint() {
@@ -1249,6 +1259,9 @@ export function useCanvasTransform(options: CanvasTransformOptions) {
1249
1259
  currentElement.value = null
1250
1260
  window.removeEventListener('mousemove', handleDragLinePoint)
1251
1261
  window.removeEventListener('mouseup', stopDragLinePoint)
1262
+ if (activeSlide.value && activeSlide.value.elements) {
1263
+ syncSlideConnections(activeSlide.value, logicalWidth.value, logicalHeight.value, selectedElementIds.value)
1264
+ }
1252
1265
  pauseHistory.value = false
1253
1266
  addHistorySnapshot()
1254
1267
  }
@@ -2568,6 +2568,18 @@ function markAllPagesDirty() {
2568
2568
  for (let i = 0; i < slides.value.length; i++) dirtyPageIndexes.add(i)
2569
2569
  }
2570
2570
 
2571
+ // 递归释放元素(含组合内子元素)的 blob: URL,防止导入替换后组内媒体泄漏
2572
+ function revokeElementBlobUrls(el: any) {
2573
+ if (!el) return
2574
+ if (Array.isArray(el.elements)) {
2575
+ for (const child of el.elements) revokeElementBlobUrls(child)
2576
+ }
2577
+ const src = el.src || el.content
2578
+ if (typeof src === 'string' && src.startsWith('blob:')) {
2579
+ try { URL.revokeObjectURL(src) } catch (e) {}
2580
+ }
2581
+ }
2582
+
2571
2583
  function revokeSlideBlobUrls(slideList: any[]) {
2572
2584
  if (!slideList || !Array.isArray(slideList)) return
2573
2585
  for (const slide of slideList) {
@@ -2585,11 +2597,7 @@ function revokeSlideBlobUrls(slideList: any[]) {
2585
2597
  }
2586
2598
  if (slide.elements && Array.isArray(slide.elements)) {
2587
2599
  for (const el of slide.elements) {
2588
- if (!el) continue
2589
- const src = el.src || el.content
2590
- if (typeof src === 'string' && src.startsWith('blob:')) {
2591
- try { URL.revokeObjectURL(src) } catch (e) {}
2592
- }
2600
+ revokeElementBlobUrls(el)
2593
2601
  }
2594
2602
  }
2595
2603
  }
@@ -92,6 +92,8 @@ export type CustomUploadHandler = (
92
92
  export interface PptEditorConfig {
93
93
  /** 演示文稿初始标题 */
94
94
  title?: string
95
+ /** 是否显示左上角主菜单按钮(导入/导出/快捷键等弹窗菜单),默认为 true */
96
+ showMenu?: boolean
95
97
  /** 是否启用 AI PPT 智能生成与一键美化功能。未配置或为 false 时不显示一键美化功能 */
96
98
  enableAi?: boolean
97
99
  /** AI 一键美化请求接口地址。如果未配置或未开启,则不显示一键美化功能 */
@@ -138,6 +140,8 @@ export interface PptEditorProps {
138
140
  readonly?: boolean
139
141
  /** 是否显示顶部导航与工具栏 */
140
142
  showHeader?: boolean
143
+ /** 是否显示左上角主菜单按钮(导入/导出/快捷键等弹窗菜单),默认为 true */
144
+ showMenu?: boolean
141
145
  /** 是否显示左侧缩略图导航栏 */
142
146
  showSidebarLeft?: boolean
143
147
  /** 是否显示右侧属性面板 */