jvs-ppt-ui 0.0.16 → 0.0.18

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.
@@ -1,213 +1,214 @@
1
- <script setup lang="ts">
2
- import { computed } from 'vue'
3
- import CanvasTextElement from './CanvasTextElement.vue'
4
- import CanvasShapeElement from './CanvasShapeElement.vue'
5
- import CanvasLineElement from './CanvasLineElement.vue'
6
- import CanvasImageElement from './CanvasImageElement.vue'
7
- import SvgChart from '../../SvgChart.vue'
8
- import { getElementImageSrc, hasElementFill, hasElementBorder, getElementDropShadowFilter } from '../../../utils/canvas/elementStyles'
9
-
10
- const props = defineProps<{
11
- element: any
12
- activeSlide?: any
13
- croppingElementId?: string | null
14
- selectedElementIds?: string[]
15
- selectedElementId?: string | null
16
- isEditingText?: boolean
17
- themeRecipe?: any
18
- getTextStrokeStyle?: (el: any) => string
19
- getElementTextShadow?: (el: any) => string
20
- getRenderedContent?: (el: any) => string
21
- // 直接父盒子的像素尺寸(顶层时为幻灯片逻辑尺寸),用于解析本组 % 样式,
22
- // 再下传给子元素解析它们的 % 宽高。与 PresentationGroupChild 的
23
- // parentW/parentH 锚同一思路:渲染期没有可查的 DOM 地图时用数据链解析。
24
- parentWidthPx?: number
25
- parentHeightPx?: number
26
- }>()
27
-
28
- function resolvePx(val: any, parentPx: number | undefined): number {
29
- if (val === undefined || val === null || val === '' || val === 'auto') return NaN
30
- if (typeof val === 'number') return val
31
- const s = String(val).trim()
32
- if (s.endsWith('%')) {
33
- const pct = parseFloat(s)
34
- const p = Number(parentPx)
35
- if (!isNaN(pct) && isFinite(p) && p > 0) return (pct / 100) * p
36
- return NaN
37
- }
38
- const n = parseFloat(s)
39
- return isNaN(n) ? NaN : n
40
- }
41
-
42
- // 本组盒子自身的像素尺寸:px 样式直接用,% 样式相对父盒解析。
43
- const selfWidthPx = computed(() => {
44
- const resolved = resolvePx((props.element as any)?.style?.width, props.parentWidthPx)
45
- if (isFinite(resolved) && resolved > 0) return resolved
46
- const p = Number(props.parentWidthPx)
47
- return isFinite(p) && p > 0 ? p : NaN
48
- })
49
-
50
- const selfHeightPx = computed(() => {
51
- const resolved = resolvePx((props.element as any)?.style?.height, props.parentHeightPx)
52
- if (isFinite(resolved) && resolved > 0) return resolved
53
- const p = Number(props.parentHeightPx)
54
- return isFinite(p) && p > 0 ? p : NaN
55
- })
56
-
57
- const emit = defineEmits<{
58
- (e: 'childMouseDown', ev: MouseEvent, child: any, parent: any): void
59
- (e: 'childClick', ev: MouseEvent, child: any, parent: any): void
60
- (e: 'childDblClick', ev: MouseEvent, child: any, parent: any): void
61
- (e: 'childContextMenu', ev: MouseEvent, child: any, parent: any): void
62
- (e: 'textFocus', child: any): void
63
- (e: 'textBlur', ev: FocusEvent, child: any): void
64
- (e: 'textInput', ev: Event, child: any): void
65
- (e: 'textKeyDown', ev: KeyboardEvent, child: any): void
66
- (e: 'textMouseMove', ev: MouseEvent, child: any): void
67
- (e: 'textMouseLeave', ev: MouseEvent, child: any): void
68
- (e: 'startEditing', id: string, ev?: MouseEvent): void
69
- (e: 'formulaDblClick', child: any): void
70
- (e: 'tableDblClick', ev: MouseEvent, child: any): void
71
- }>()
72
-
73
- function sortGroupElements(elements: any[]) {
74
- if (!elements || !Array.isArray(elements)) return []
75
- return [...elements].sort((a: any, b: any) => {
76
- const orderA = a.order !== undefined ? a.order : (parseFloat(a.style?.zIndex) || 0)
77
- const orderB = b.order !== undefined ? b.order : (parseFloat(b.style?.zIndex) || 0)
78
- return orderA - orderB
79
- })
80
- }
81
- </script>
82
-
83
- <template>
84
- <div class="group-container-wrapper" style="width: 100%; height: 100%; position: relative; overflow: visible; pointer-events: none;">
85
- <div
86
- v-for="child in sortGroupElements(element.elements || [])"
87
- :key="child.id"
88
- :id="`canvas-el-${child.id}`"
89
- class="group-child-item"
90
- :style="{
91
- position: 'absolute',
92
- left: child.style?.left,
93
- top: child.style?.top,
94
- width: child.style?.width,
95
- height: (child.type === 'text' && (child.autoFit?.type === 'shape' || child.style?.height === 'auto') && !child.content?.includes('<table') && !child.latex && child.style?.writingMode !== 'vertical-rl' && child.style?.writingMode !== 'vertical-lr') ? 'auto' : (child.style?.height || (child.height ? `${child.height}px` : 'auto')),
96
- minHeight: (child.type === 'text' && !child.content?.includes('<table') && !child.latex) ? child.style?.height : undefined,
97
- transform: `rotate(${child.style?.rotate || 0}deg) scale(${child.type !== 'text' && child.type !== 'line' && ((child.style as any)?.flipH || child.flipH || child.isFlipH) ? -1 : 1}, ${child.type !== 'text' && child.type !== 'line' && ((child.style as any)?.flipV || child.flipV || child.isFlipV) ? -1 : 1})`,
98
- opacity: child.style?.opacity !== undefined ? child.style.opacity : 1,
99
- filter: getElementDropShadowFilter(child),
100
- pointerEvents: child.type === 'group' ? 'none' : (((child.type === 'shape' || !child.type) && !hasElementFill(child) && !hasElementBorder(child)) ? 'none' : 'auto')
101
- }"
102
- @mousedown.stop="emit('childMouseDown', $event, child, element)"
103
- @click.stop="emit('childClick', $event, child, element)"
104
- @dblclick.stop="emit('childDblClick', $event, child, element)"
105
- @contextmenu.stop.prevent="emit('childContextMenu', $event, child, element)"
106
- >
107
- <!-- Child Text -->
108
- <template v-if="child.type === 'text'">
109
- <CanvasTextElement
110
- :element="child"
111
- :active-slide="activeSlide"
112
- :is-selected="selectedElementId === child.id"
113
- :is-editing-text="isEditingText"
114
- :selected-element-id="selectedElementId"
115
- :theme-recipe="themeRecipe"
116
- :rendered-content="getRenderedContent ? getRenderedContent(child) : child.content"
117
- :get-text-stroke-style="getTextStrokeStyle"
118
- :get-element-text-shadow="getElementTextShadow"
119
- @focusin="emit('textFocus', child)"
120
- @focusout="emit('textBlur', $event, child)"
121
- @input="emit('textInput', $event, child)"
122
- @keydown="emit('textKeyDown', $event, child)"
123
- @mousemove="emit('textMouseMove', $event, child)"
124
- @mouseleave="emit('textMouseLeave', $event, child)"
125
- @dblclick="emit('startEditing', child.id)"
126
- @formula-dbl-click="emit('formulaDblClick', child)"
127
- @table-dbl-click="emit('tableDblClick', $event, child)"
128
- />
129
- </template>
130
-
131
- <!-- Child Shape -->
132
- <template v-else-if="child.type === 'shape'">
133
- <CanvasShapeElement
134
- :element="child"
135
- :active-slide="activeSlide"
136
- :is-selected="selectedElementId === child.id"
137
- :is-editing-text="isEditingText"
138
- :selected-element-id="selectedElementId"
139
- :theme-recipe="themeRecipe"
140
- :parent-width-px="selfWidthPx"
141
- :parent-height-px="selfHeightPx"
142
- :get-text-stroke-style="getTextStrokeStyle"
143
- :get-element-text-shadow="getElementTextShadow"
144
- @text-focus="emit('textFocus', child)"
145
- @text-blur="emit('textBlur', $event, child)"
146
- @text-input="emit('textInput', $event, child)"
147
- @text-key-down="emit('textKeyDown', $event, child)"
148
- @start-editing="(ev) => emit('startEditing', child.id, ev)"
149
- />
150
- </template>
151
-
152
- <!-- Child Line -->
153
- <template v-else-if="child.type === 'line'">
154
- <CanvasLineElement :element="child" :all-elements="element.elements" />
155
- </template>
156
-
157
- <!-- Child Image -->
158
- <template
159
- v-else-if="child.type === 'image' || child.type === 'pic' || child.type === 'picture' || (child.type !== 'text' && child.type !== 'line' && child.type !== 'shape' && child.type !== 'group' && child.type !== 'chart' && Boolean(getElementImageSrc(child)))">
160
- <CanvasImageElement :element="child" :cropping-element-id="croppingElementId" />
161
- </template>
162
-
163
- <!-- Child Chart -->
164
- <template v-else-if="child.type === 'chart'">
165
- <div style="width: 100%; height: 100%; overflow: visible; box-sizing: border-box;" :style="{
166
- backgroundColor: child.style?.fill || 'transparent',
167
- borderWidth: child.style?.borderWidth || '0px',
168
- borderStyle: child.style?.borderStyle || 'solid',
169
- borderColor: child.style?.borderColor || 'transparent',
170
- borderRadius: child.style?.borderRadius || '0px',
171
- boxShadow: child.style?.boxShadow || 'none'
172
- }">
173
- <SvgChart :element="child" />
174
- </div>
175
- </template>
176
-
177
- <!-- Nested Group -->
178
- <template v-else-if="child.type === 'group'">
179
- <CanvasGroupElement
180
- :element="child"
181
- :active-slide="activeSlide"
182
- :cropping-element-id="croppingElementId"
183
- :parent-width-px="selfWidthPx"
184
- :parent-height-px="selfHeightPx"
185
- :selected-element-ids="selectedElementIds"
186
- :selected-element-id="selectedElementId"
187
- :is-editing-text="isEditingText"
188
- :theme-recipe="themeRecipe"
189
- :get-text-stroke-style="getTextStrokeStyle"
190
- :get-element-text-shadow="getElementTextShadow"
191
- :get-rendered-content="getRenderedContent"
192
- @child-mouse-down="(ev, c, p) => emit('childMouseDown', ev, c, p)"
193
- @child-click="(ev, c, p) => emit('childClick', ev, c, p)"
194
- @child-dbl-click="(ev, c, p) => emit('childDblClick', ev, c, p)"
195
- @child-context-menu="(ev, c, p) => emit('childContextMenu', ev, c, p)"
196
- @text-focus="(c) => emit('textFocus', c)"
197
- @text-blur="(ev, c) => emit('textBlur', ev, c)"
198
- @text-input="(ev, c) => emit('textInput', ev, c)"
199
- @text-key-down="(ev, c) => emit('textKeyDown', ev, c)"
200
- @text-mouse-move="(ev, c) => emit('textMouseMove', ev, c)"
201
- @text-mouse-leave="(ev, c) => emit('textMouseLeave', ev, c)"
202
- @start-editing="(id, ev) => emit('startEditing', id, ev)"
203
- />
204
- </template>
205
- </div>
206
- </div>
207
- </template>
208
-
209
- <style scoped>
210
- .group-child-item {
211
- box-sizing: border-box;
212
- }
213
- </style>
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import CanvasTextElement from './CanvasTextElement.vue'
4
+ import CanvasShapeElement from './CanvasShapeElement.vue'
5
+ import CanvasLineElement from './CanvasLineElement.vue'
6
+ import CanvasImageElement from './CanvasImageElement.vue'
7
+ import SvgChart from '../../SvgChart.vue'
8
+ import { getElementImageSrc, hasElementFill, hasElementBorder, getElementDropShadowFilter, isImagePlaceholder } from '../../../utils/canvas/elementStyles'
9
+
10
+ const props = defineProps<{
11
+ element: any
12
+ activeSlide?: any
13
+ croppingElementId?: string | null
14
+ selectedElementIds?: string[]
15
+ selectedElementId?: string | null
16
+ isEditingText?: boolean
17
+ themeRecipe?: any
18
+ getTextStrokeStyle?: (el: any) => string
19
+ getElementTextShadow?: (el: any) => string
20
+ getRenderedContent?: (el: any) => string
21
+ // 直接父盒子的像素尺寸(顶层时为幻灯片逻辑尺寸),用于解析本组 % 样式,
22
+ // 再下传给子元素解析它们的 % 宽高。与 PresentationGroupChild 的
23
+ // parentW/parentH 锚同一思路:渲染期没有可查的 DOM 地图时用数据链解析。
24
+ parentWidthPx?: number
25
+ parentHeightPx?: number
26
+ }>()
27
+
28
+ function resolvePx(val: any, parentPx: number | undefined): number {
29
+ if (val === undefined || val === null || val === '' || val === 'auto') return NaN
30
+ if (typeof val === 'number') return val
31
+ const s = String(val).trim()
32
+ if (s.endsWith('%')) {
33
+ const pct = parseFloat(s)
34
+ const p = Number(parentPx)
35
+ if (!isNaN(pct) && isFinite(p) && p > 0) return (pct / 100) * p
36
+ return NaN
37
+ }
38
+ const n = parseFloat(s)
39
+ return isNaN(n) ? NaN : n
40
+ }
41
+
42
+ // 本组盒子自身的像素尺寸:px 样式直接用,% 样式相对父盒解析。
43
+ const selfWidthPx = computed(() => {
44
+ const resolved = resolvePx((props.element as any)?.style?.width, props.parentWidthPx)
45
+ if (isFinite(resolved) && resolved > 0) return resolved
46
+ const p = Number(props.parentWidthPx)
47
+ return isFinite(p) && p > 0 ? p : NaN
48
+ })
49
+
50
+ const selfHeightPx = computed(() => {
51
+ const resolved = resolvePx((props.element as any)?.style?.height, props.parentHeightPx)
52
+ if (isFinite(resolved) && resolved > 0) return resolved
53
+ const p = Number(props.parentHeightPx)
54
+ return isFinite(p) && p > 0 ? p : NaN
55
+ })
56
+
57
+ const emit = defineEmits<{
58
+ (e: 'childMouseDown', ev: MouseEvent, child: any, parent: any): void
59
+ (e: 'childClick', ev: MouseEvent, child: any, parent: any): void
60
+ (e: 'childDblClick', ev: MouseEvent, child: any, parent: any): void
61
+ (e: 'childContextMenu', ev: MouseEvent, child: any, parent: any): void
62
+ (e: 'textFocus', child: any): void
63
+ (e: 'textBlur', ev: FocusEvent, child: any): void
64
+ (e: 'textInput', ev: Event, child: any): void
65
+ (e: 'textKeyDown', ev: KeyboardEvent, child: any): void
66
+ (e: 'textMouseMove', ev: MouseEvent, child: any): void
67
+ (e: 'textMouseLeave', ev: MouseEvent, child: any): void
68
+ (e: 'startEditing', id: string, ev?: MouseEvent): void
69
+ (e: 'formulaDblClick', child: any): void
70
+ (e: 'tableDblClick', ev: MouseEvent, child: any): void
71
+ }>()
72
+
73
+ function sortGroupElements(elements: any[]) {
74
+ if (!elements || !Array.isArray(elements)) return []
75
+ return [...elements].sort((a: any, b: any) => {
76
+ const orderA = a.order !== undefined ? a.order : (parseFloat(a.style?.zIndex) || 0)
77
+ const orderB = b.order !== undefined ? b.order : (parseFloat(b.style?.zIndex) || 0)
78
+ return orderA - orderB
79
+ })
80
+ }
81
+ </script>
82
+
83
+ <template>
84
+ <div class="group-container-wrapper" style="width: 100%; height: 100%; position: relative; overflow: visible; pointer-events: none;">
85
+ <div
86
+ v-for="child in sortGroupElements(element.elements || [])"
87
+ :key="child.id"
88
+ :id="`canvas-el-${child.id}`"
89
+ class="group-child-item"
90
+ :style="{
91
+ position: 'absolute',
92
+ left: child.style?.left,
93
+ top: child.style?.top,
94
+ width: child.style?.width,
95
+ height: (child.type === 'text' && (child.autoFit?.type === 'shape' || child.style?.height === 'auto') && !child.content?.includes('<table') && !child.latex && child.style?.writingMode !== 'vertical-rl' && child.style?.writingMode !== 'vertical-lr') ? 'auto' : (child.style?.height || (child.height ? `${child.height}px` : 'auto')),
96
+ minHeight: (child.type === 'text' && !child.content?.includes('<table') && !child.latex) ? child.style?.height : undefined,
97
+ transform: `rotate(${child.style?.rotate || 0}deg) scale(${child.type !== 'text' && child.type !== 'line' && ((child.style as any)?.flipH || child.flipH || child.isFlipH) ? -1 : 1}, ${child.type !== 'text' && child.type !== 'line' && ((child.style as any)?.flipV || child.flipV || child.isFlipV) ? -1 : 1})`,
98
+ opacity: child.style?.opacity !== undefined ? child.style.opacity : 1,
99
+ filter: getElementDropShadowFilter(child),
100
+ pointerEvents: child.type === 'group' ? 'none' : (((child.type === 'shape' || !child.type) && !hasElementFill(child) && !hasElementBorder(child)) ? 'none' : 'auto')
101
+ }"
102
+ @mousedown.stop="emit('childMouseDown', $event, child, element)"
103
+ @click.stop="emit('childClick', $event, child, element)"
104
+ @dblclick.stop="emit('childDblClick', $event, child, element)"
105
+ @contextmenu.stop.prevent="emit('childContextMenu', $event, child, element)"
106
+ >
107
+ <!-- Child Text -->
108
+ <template v-if="child.type === 'text'">
109
+ <CanvasTextElement
110
+ :element="child"
111
+ :active-slide="activeSlide"
112
+ :is-selected="selectedElementId === child.id"
113
+ :is-editing-text="isEditingText"
114
+ :selected-element-id="selectedElementId"
115
+ :theme-recipe="themeRecipe"
116
+ :rendered-content="getRenderedContent ? getRenderedContent(child) : child.content"
117
+ :get-text-stroke-style="getTextStrokeStyle"
118
+ :get-element-text-shadow="getElementTextShadow"
119
+ @focusin="emit('textFocus', child)"
120
+ @focusout="emit('textBlur', $event, child)"
121
+ @input="emit('textInput', $event, child)"
122
+ @keydown="emit('textKeyDown', $event, child)"
123
+ @mousemove="emit('textMouseMove', $event, child)"
124
+ @mouseleave="emit('textMouseLeave', $event, child)"
125
+ @dblclick="emit('startEditing', child.id)"
126
+ @formula-dbl-click="emit('formulaDblClick', child)"
127
+ @table-dbl-click="emit('tableDblClick', $event, child)"
128
+ />
129
+ </template>
130
+
131
+ <!-- Child Shape -->
132
+ <template v-else-if="child.type === 'shape'">
133
+ <CanvasShapeElement
134
+ :element="child"
135
+ :active-slide="activeSlide"
136
+ :is-selected="selectedElementId === child.id"
137
+ :is-editing-text="isEditingText"
138
+ :selected-element-id="selectedElementId"
139
+ :theme-recipe="themeRecipe"
140
+ :parent-width-px="selfWidthPx"
141
+ :parent-height-px="selfHeightPx"
142
+ :get-text-stroke-style="getTextStrokeStyle"
143
+ :get-element-text-shadow="getElementTextShadow"
144
+ :is-image-placeholder="isImagePlaceholder"
145
+ @text-focus="emit('textFocus', child)"
146
+ @text-blur="emit('textBlur', $event, child)"
147
+ @text-input="emit('textInput', $event, child)"
148
+ @text-key-down="emit('textKeyDown', $event, child)"
149
+ @start-editing="(ev) => emit('startEditing', child.id, ev)"
150
+ />
151
+ </template>
152
+
153
+ <!-- Child Line -->
154
+ <template v-else-if="child.type === 'line'">
155
+ <CanvasLineElement :element="child" :all-elements="element.elements" />
156
+ </template>
157
+
158
+ <!-- Child Image -->
159
+ <template
160
+ v-else-if="child.type === 'image' || child.type === 'pic' || child.type === 'picture' || (child.type !== 'text' && child.type !== 'line' && child.type !== 'shape' && child.type !== 'group' && child.type !== 'chart' && Boolean(getElementImageSrc(child)))">
161
+ <CanvasImageElement :element="child" :cropping-element-id="croppingElementId" />
162
+ </template>
163
+
164
+ <!-- Child Chart -->
165
+ <template v-else-if="child.type === 'chart'">
166
+ <div style="width: 100%; height: 100%; overflow: visible; box-sizing: border-box;" :style="{
167
+ backgroundColor: child.style?.fill || 'transparent',
168
+ borderWidth: child.style?.borderWidth || '0px',
169
+ borderStyle: child.style?.borderStyle || 'solid',
170
+ borderColor: child.style?.borderColor || 'transparent',
171
+ borderRadius: child.style?.borderRadius || '0px',
172
+ boxShadow: child.style?.boxShadow || 'none'
173
+ }">
174
+ <SvgChart :element="child" />
175
+ </div>
176
+ </template>
177
+
178
+ <!-- Nested Group -->
179
+ <template v-else-if="child.type === 'group'">
180
+ <CanvasGroupElement
181
+ :element="child"
182
+ :active-slide="activeSlide"
183
+ :cropping-element-id="croppingElementId"
184
+ :parent-width-px="selfWidthPx"
185
+ :parent-height-px="selfHeightPx"
186
+ :selected-element-ids="selectedElementIds"
187
+ :selected-element-id="selectedElementId"
188
+ :is-editing-text="isEditingText"
189
+ :theme-recipe="themeRecipe"
190
+ :get-text-stroke-style="getTextStrokeStyle"
191
+ :get-element-text-shadow="getElementTextShadow"
192
+ :get-rendered-content="getRenderedContent"
193
+ @child-mouse-down="(ev, c, p) => emit('childMouseDown', ev, c, p)"
194
+ @child-click="(ev, c, p) => emit('childClick', ev, c, p)"
195
+ @child-dbl-click="(ev, c, p) => emit('childDblClick', ev, c, p)"
196
+ @child-context-menu="(ev, c, p) => emit('childContextMenu', ev, c, p)"
197
+ @text-focus="(c) => emit('textFocus', c)"
198
+ @text-blur="(ev, c) => emit('textBlur', ev, c)"
199
+ @text-input="(ev, c) => emit('textInput', ev, c)"
200
+ @text-key-down="(ev, c) => emit('textKeyDown', ev, c)"
201
+ @text-mouse-move="(ev, c) => emit('textMouseMove', ev, c)"
202
+ @text-mouse-leave="(ev, c) => emit('textMouseLeave', ev, c)"
203
+ @start-editing="(id, ev) => emit('startEditing', id, ev)"
204
+ />
205
+ </template>
206
+ </div>
207
+ </div>
208
+ </template>
209
+
210
+ <style scoped>
211
+ .group-child-item {
212
+ box-sizing: border-box;
213
+ }
214
+ </style>