@slidev/client 0.48.9 → 0.49.0-beta.2

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.
@@ -0,0 +1,397 @@
1
+ <script setup lang="ts">
2
+ import { clamp } from '@antfu/utils'
3
+ import type { Pausable } from '@vueuse/core'
4
+ import { useIntervalFn } from '@vueuse/core'
5
+ import { computed, inject, ref, watchEffect } from 'vue'
6
+ import type { DragElementState } from '../composables/useDragElements'
7
+ import { useSlideBounds } from '../composables/useSlideBounds'
8
+ import { injectionSlideScale } from '../constants'
9
+ import { slideHeight, slideWidth } from '../env'
10
+ import { magicKeys } from '../state'
11
+
12
+ const { data } = defineProps<{ data: DragElementState }>()
13
+ const { id, zoom, autoHeight, x0, y0, width, height, rotate } = data
14
+
15
+ const slideScale = inject(injectionSlideScale, ref(1))
16
+ const scale = computed(() => slideScale.value * zoom.value)
17
+ const { left: slideLeft, top: slideTop } = useSlideBounds()
18
+
19
+ const ctrlSize = 10
20
+ const minSize = 40
21
+ const minRemain = 10
22
+
23
+ const rotateRad = computed(() => rotate.value * Math.PI / 180)
24
+ const rotateSin = computed(() => Math.sin(rotateRad.value))
25
+ const rotateCos = computed(() => Math.cos(rotateRad.value))
26
+
27
+ const boundingWidth = computed(() => width.value * rotateCos.value + height.value * rotateSin.value)
28
+ const boundingHeight = computed(() => width.value * rotateSin.value + height.value * rotateCos.value)
29
+
30
+ const boundingLeft = computed(() => x0.value - boundingWidth.value / 2)
31
+ const boundingTop = computed(() => y0.value - boundingHeight.value / 2)
32
+ const boundingRight = computed(() => x0.value + boundingWidth.value / 2)
33
+ const boundingBottom = computed(() => y0.value + boundingHeight.value / 2)
34
+
35
+ let currentDrag: {
36
+ x0: number
37
+ y0: number
38
+ width: number
39
+ height: number
40
+ rotate: number
41
+ dx0: number
42
+ dy0: number
43
+ ltx: number
44
+ lty: number
45
+ rtx: number
46
+ rty: number
47
+ lbx: number
48
+ lby: number
49
+ rbx: number
50
+ rby: number
51
+ } | null = null
52
+
53
+ function onPointerdown(ev: PointerEvent) {
54
+ if (ev.buttons !== 1)
55
+ return
56
+
57
+ ev.preventDefault()
58
+ ev.stopPropagation()
59
+ const el = ev.target as HTMLElement
60
+ const elBounds = el.getBoundingClientRect()
61
+
62
+ const cross1x = width.value * rotateCos.value - height.value * rotateSin.value
63
+ const cross1y = width.value * rotateSin.value + height.value * rotateCos.value
64
+ const cross2x = width.value * rotateCos.value + height.value * rotateSin.value
65
+ const cross2y = -width.value * rotateSin.value + height.value * rotateCos.value
66
+
67
+ currentDrag = {
68
+ x0: x0.value,
69
+ y0: y0.value,
70
+ width: width.value,
71
+ height: height.value,
72
+ rotate: rotate.value,
73
+ dx0: ev.clientX - (elBounds.left + elBounds.right) / 2,
74
+ dy0: ev.clientY - (elBounds.top + elBounds.bottom) / 2,
75
+ ltx: x0.value - cross1x / 2,
76
+ lty: y0.value - cross1y / 2,
77
+ rtx: x0.value + cross2x / 2,
78
+ rty: y0.value - cross2y / 2,
79
+ lbx: x0.value - cross2x / 2,
80
+ lby: y0.value + cross2y / 2,
81
+ rbx: x0.value + cross1x / 2,
82
+ rby: y0.value + cross1y / 2,
83
+ };
84
+
85
+ (ev.currentTarget as HTMLElement).setPointerCapture(ev.pointerId)
86
+ }
87
+
88
+ function onPointermove(ev: PointerEvent) {
89
+ if (!currentDrag || ev.buttons !== 1)
90
+ return
91
+
92
+ ev.preventDefault()
93
+ ev.stopPropagation()
94
+
95
+ const x = (ev.clientX - slideLeft.value - currentDrag.dx0) / scale.value
96
+ const y = (ev.clientY - slideTop.value - currentDrag.dy0) / scale.value
97
+
98
+ x0.value = clamp(x, -boundingWidth.value / 2 + minRemain, slideWidth.value + boundingWidth.value / 2 - minRemain)
99
+ y0.value = clamp(y, -boundingHeight.value / 2 + minRemain, slideHeight.value + boundingHeight.value / 2 - minRemain)
100
+ }
101
+
102
+ function onPointerup(ev: PointerEvent) {
103
+ if (!currentDrag)
104
+ return
105
+
106
+ ev.preventDefault()
107
+ ev.stopPropagation()
108
+
109
+ currentDrag = null
110
+ }
111
+
112
+ const ctrlClasses = `absolute border border-gray bg-gray dark:border-gray-500 dark:bg-gray-800 bg-opacity-30 `
113
+
114
+ function getCornerProps(isLeft: boolean, isTop: boolean) {
115
+ return {
116
+ onPointerdown,
117
+ onPointermove: (ev: PointerEvent) => {
118
+ if (!currentDrag || ev.buttons !== 1)
119
+ return
120
+
121
+ ev.preventDefault()
122
+ ev.stopPropagation()
123
+
124
+ let x = (ev.clientX - slideLeft.value) / scale.value
125
+ let y = (ev.clientY - slideTop.value) / scale.value
126
+
127
+ const { ltx, lty, rtx, rty, lbx, lby, rbx, rby } = currentDrag
128
+
129
+ const ratio = currentDrag.width / currentDrag.height
130
+ const wMin = Math.max(minSize, minSize * ratio)
131
+ function getSize(w1: number, h1: number) {
132
+ if (ev.shiftKey) {
133
+ const w = Math.max(w1, h1 * ratio, wMin)
134
+ const h = w / ratio
135
+ return { w, h }
136
+ }
137
+ else {
138
+ return { w: Math.max(w1, minSize), h: Math.max(h1, minSize) }
139
+ }
140
+ }
141
+
142
+ if (isLeft) {
143
+ if (isTop) {
144
+ const w1 = (rbx - x) * rotateCos.value + (rby - y) * rotateSin.value
145
+ const h1 = -(rbx - x) * rotateSin.value + (rby - y) * rotateCos.value
146
+ const { w, h } = getSize(w1, h1)
147
+ x = rbx - w * rotateCos.value + h * rotateSin.value
148
+ y = rby - w * rotateSin.value - h * rotateCos.value
149
+ }
150
+ else {
151
+ const w1 = (rtx - x) * rotateCos.value - (y - rty) * rotateSin.value
152
+ const h1 = (rtx - x) * rotateSin.value + (y - rty) * rotateCos.value
153
+ const { w, h } = getSize(w1, h1)
154
+ x = rtx - w * rotateCos.value - h * rotateSin.value
155
+ y = rty - w * rotateSin.value + h * rotateCos.value
156
+ }
157
+ }
158
+ else {
159
+ if (isTop) {
160
+ const w1 = (x - lbx) * rotateCos.value - (lby - y) * rotateSin.value
161
+ const h1 = (x - lbx) * rotateSin.value + (lby - y) * rotateCos.value
162
+ const { w, h } = getSize(w1, h1)
163
+ x = lbx + w * rotateCos.value + h * rotateSin.value
164
+ y = lby + w * rotateSin.value - h * rotateCos.value
165
+ }
166
+ else {
167
+ const w1 = (x - ltx) * rotateCos.value + (y - lty) * rotateSin.value
168
+ const h1 = -(x - ltx) * rotateSin.value + (y - lty) * rotateCos.value
169
+ const { w, h } = getSize(w1, h1)
170
+ x = ltx + w * rotateCos.value - h * rotateSin.value
171
+ y = lty + w * rotateSin.value + h * rotateCos.value
172
+ }
173
+ }
174
+
175
+ if (isLeft) {
176
+ if (isTop) {
177
+ x0.value = (x + rbx) / 2
178
+ y0.value = (y + rby) / 2
179
+ width.value = (rbx - x) * rotateCos.value + (rby - y) * rotateSin.value
180
+ height.value = -(rbx - x) * rotateSin.value + (rby - y) * rotateCos.value
181
+ }
182
+ else {
183
+ x0.value = (x + rtx) / 2
184
+ y0.value = (y + rty) / 2
185
+ width.value = (rtx - x) * rotateCos.value - (y - rty) * rotateSin.value
186
+ height.value = (rtx - x) * rotateSin.value + (y - rty) * rotateCos.value
187
+ }
188
+ }
189
+ else {
190
+ if (isTop) {
191
+ x0.value = (x + lbx) / 2
192
+ y0.value = (y + lby) / 2
193
+ width.value = (x - lbx) * rotateCos.value - (lby - y) * rotateSin.value
194
+ height.value = (x - lbx) * rotateSin.value + (lby - y) * rotateCos.value
195
+ }
196
+ else {
197
+ x0.value = (x + ltx) / 2
198
+ y0.value = (y + lty) / 2
199
+ width.value = (x - ltx) * rotateCos.value + (y - lty) * rotateSin.value
200
+ height.value = -(x - ltx) * rotateSin.value + (y - lty) * rotateCos.value
201
+ }
202
+ }
203
+ },
204
+ onPointerup,
205
+ style: {
206
+ width: `${ctrlSize}px`,
207
+ height: `${ctrlSize}px`,
208
+ margin: `-${ctrlSize / 2}px`,
209
+ left: isLeft ? '0' : undefined,
210
+ right: isLeft ? undefined : '0',
211
+ top: isTop ? '0' : undefined,
212
+ bottom: isTop ? undefined : '0',
213
+ cursor: +isLeft + +isTop === 1 ? 'nesw-resize' : 'nwse-resize',
214
+ },
215
+ class: ctrlClasses,
216
+ }
217
+ }
218
+
219
+ function getBorderProps(dir: 'l' | 'r' | 't' | 'b') {
220
+ return {
221
+ onPointerdown,
222
+ onPointermove: (ev: PointerEvent) => {
223
+ if (!currentDrag || ev.buttons !== 1)
224
+ return
225
+
226
+ ev.preventDefault()
227
+ ev.stopPropagation()
228
+
229
+ const x = (ev.clientX - slideLeft.value) / scale.value
230
+ const y = (ev.clientY - slideTop.value) / scale.value
231
+
232
+ const { ltx, lty, rtx, rty, lbx, lby, rbx, rby } = currentDrag
233
+
234
+ if (dir === 'l') {
235
+ const rx = (rtx + rbx) / 2
236
+ const ry = (rty + rby) / 2
237
+ width.value = Math.max((rx - x) * rotateCos.value + (ry - y) * rotateSin.value, minSize)
238
+ x0.value = rx - width.value * rotateCos.value / 2
239
+ y0.value = ry - width.value * rotateSin.value / 2
240
+ }
241
+ else if (dir === 'r') {
242
+ const lx = (ltx + lbx) / 2
243
+ const ly = (lty + lby) / 2
244
+ width.value = Math.max((x - lx) * rotateCos.value + (y - ly) * rotateSin.value, minSize)
245
+ x0.value = lx + width.value * rotateCos.value / 2
246
+ y0.value = ly + width.value * rotateSin.value / 2
247
+ }
248
+ else if (dir === 't') {
249
+ const bx = (lbx + rbx) / 2
250
+ const by = (lby + rby) / 2
251
+ height.value = Math.max((by - y) * rotateCos.value - (bx - x) * rotateSin.value, minSize)
252
+ x0.value = bx + height.value * rotateSin.value / 2
253
+ y0.value = by - height.value * rotateCos.value / 2
254
+ }
255
+ else if (dir === 'b') {
256
+ const tx = (ltx + rtx) / 2
257
+ const ty = (lty + rty) / 2
258
+ height.value = Math.max((y - ty) * rotateCos.value - (x - tx) * rotateSin.value, minSize)
259
+ x0.value = tx - height.value * rotateSin.value / 2
260
+ y0.value = ty + height.value * rotateCos.value / 2
261
+ }
262
+ },
263
+ onPointerup,
264
+ style: {
265
+ width: `${ctrlSize}px`,
266
+ height: `${ctrlSize}px`,
267
+ margin: `-${ctrlSize / 2}px`,
268
+ left: dir === 'l' ? '0' : dir === 'r' ? `100%` : `50%`,
269
+ top: dir === 't' ? '0' : dir === 'b' ? `100%` : `50%`,
270
+ cursor: 'lr'.includes(dir) ? 'ew-resize' : 'ns-resize',
271
+ borderRadius: '50%',
272
+ },
273
+ class: ctrlClasses,
274
+ }
275
+ }
276
+
277
+ function getRotateProps() {
278
+ return {
279
+ onPointerdown,
280
+ onPointermove: (ev: PointerEvent) => {
281
+ if (!currentDrag || ev.buttons !== 1)
282
+ return
283
+
284
+ ev.preventDefault()
285
+ ev.stopPropagation()
286
+
287
+ const x = (ev.clientX - slideLeft.value - currentDrag.dx0) / scale.value - ctrlSize / 4
288
+ const y = (ev.clientY - slideTop.value - currentDrag.dy0) / scale.value - ctrlSize / 4
289
+
290
+ let angle = Math.atan2(y - y0.value, x - x0.value) * 180 / Math.PI + 90
291
+
292
+ const commonAngles = [0, 90, 180, 270, 360]
293
+ for (const a of commonAngles) {
294
+ if (Math.abs(angle - a) < 5) {
295
+ angle = a % 360
296
+ break
297
+ }
298
+ }
299
+
300
+ rotate.value = angle
301
+ },
302
+ onPointerup,
303
+ style: {
304
+ width: `${ctrlSize}px`,
305
+ height: `${ctrlSize}px`,
306
+ margin: `-${ctrlSize / 2}px`,
307
+ left: '50%',
308
+ top: '-20px',
309
+ cursor: 'grab',
310
+ borderRadius: '50%',
311
+ },
312
+ class: ctrlClasses,
313
+ }
314
+ }
315
+
316
+ const moveInterval = 20
317
+ const intervalFnOptions = {
318
+ immediate: false,
319
+ immediateCallback: false,
320
+ }
321
+ const moveLeft = useIntervalFn(() => {
322
+ if (boundingRight.value <= minRemain)
323
+ return
324
+ x0.value--
325
+ }, moveInterval, intervalFnOptions)
326
+ const moveRight = useIntervalFn(() => {
327
+ if (boundingLeft.value >= slideWidth.value - minRemain)
328
+ return
329
+ x0.value++
330
+ }, moveInterval, intervalFnOptions)
331
+ const moveUp = useIntervalFn(() => {
332
+ if (boundingBottom.value <= minRemain)
333
+ return
334
+ y0.value--
335
+ }, moveInterval, intervalFnOptions)
336
+ const moveDown = useIntervalFn(() => {
337
+ if (boundingTop.value >= slideHeight.value - minRemain)
338
+ return
339
+ y0.value++
340
+ }, moveInterval, intervalFnOptions)
341
+
342
+ watchEffect(() => {
343
+ function shortcut(key: string, fn: Pausable) {
344
+ if (magicKeys[key].value)
345
+ fn.resume()
346
+ else fn.pause()
347
+ }
348
+ shortcut('left', moveLeft)
349
+ shortcut('right', moveRight)
350
+ shortcut('up', moveUp)
351
+ shortcut('down', moveDown)
352
+ })
353
+ </script>
354
+
355
+ <template>
356
+ <div
357
+ v-if="Number.isFinite(x0)"
358
+ id="drag-control-container"
359
+ :data-drag-id="id"
360
+ :style="{
361
+ position: 'absolute',
362
+ zIndex: 100,
363
+ left: `${zoom * (x0 - width / 2)}px`,
364
+ top: `${zoom * (y0 - height / 2)}px`,
365
+ width: `${zoom * width}px`,
366
+ height: `${zoom * height}px`,
367
+ transformOrigin: 'center center',
368
+ transform: `rotate(${rotate}deg)`,
369
+ }"
370
+ @pointerdown="onPointerdown"
371
+ @pointermove="onPointermove"
372
+ @pointerup="onPointerup"
373
+ >
374
+ <div class="absolute inset-0 z-100 b b-dark dark:b-gray-400">
375
+ <template v-if="!autoHeight">
376
+ <div v-bind="getCornerProps(true, true)" />
377
+ <div v-bind="getCornerProps(true, false)" />
378
+ <div v-bind="getCornerProps(false, true)" />
379
+ <div v-bind="getCornerProps(false, false)" />
380
+ </template>
381
+ <div v-bind="getBorderProps('l')" />
382
+ <div v-bind="getBorderProps('r')" />
383
+ <template v-if="!autoHeight">
384
+ <div v-bind="getBorderProps('t')" />
385
+ <div v-bind="getBorderProps('b')" />
386
+ </template>
387
+ <div v-bind="getRotateProps()" />
388
+ <div
389
+ class="absolute -top-15px w-0 b b-dashed b-dark dark:b-gray-400"
390
+ :style="{
391
+ left: 'calc(50% - 1px)',
392
+ height: autoHeight ? '14px' : '10px',
393
+ }"
394
+ />
395
+ </div>
396
+ </div>
397
+ </template>
@@ -5,7 +5,6 @@ import { downloadPDF } from '../utils'
5
5
  import { activeElement, breakpoints, fullscreen, presenterLayout, showEditor, showInfoDialog, showPresenterCursor, toggleOverview, togglePresenterLayout } from '../state'
6
6
  import { configs } from '../env'
7
7
  import { useNav } from '../composables/useNav'
8
- import { getSlidePath } from '../logic/slides'
9
8
  import { useDrawings } from '../composables/useDrawings'
10
9
  import Settings from './Settings.vue'
11
10
  import MenuButton from './MenuButton.vue'
@@ -21,7 +20,6 @@ const props = defineProps({
21
20
  })
22
21
 
23
22
  const {
24
- currentRoute,
25
23
  currentSlideNo,
26
24
  hasNext,
27
25
  hasPrev,
@@ -31,6 +29,8 @@ const {
31
29
  next,
32
30
  prev,
33
31
  total,
32
+ enterPresenter,
33
+ exitPresenter,
34
34
  } = useNav()
35
35
  const {
36
36
  brush,
@@ -40,11 +40,6 @@ const {
40
40
  const md = breakpoints.smaller('md')
41
41
  const { isFullscreen, toggle: toggleFullscreen } = fullscreen
42
42
 
43
- const presenterPassword = computed(() => currentRoute.value.query.password)
44
- const query = computed(() => presenterPassword.value ? `?password=${presenterPassword.value}` : '')
45
- const presenterLink = computed(() => `${getSlidePath(currentSlideNo.value, true)}${query.value}`)
46
- const nonPresenterLink = computed(() => `${getSlidePath(currentSlideNo.value, false)}${query.value}`)
47
-
48
43
  const root = ref<HTMLDivElement>()
49
44
  function onMouseLeave() {
50
45
  if (root.value && activeElement.value && root.value.contains(activeElement.value))
@@ -124,12 +119,12 @@ if (__SLIDEV_FEATURE_DRAWINGS__)
124
119
  </template>
125
120
 
126
121
  <template v-if="!isEmbedded">
127
- <RouterLink v-if="isPresenter" :to="nonPresenterLink" class="slidev-icon-btn" title="Play Mode">
122
+ <IconButton v-if="isPresenter" title="Play Mode" @click="exitPresenter">
128
123
  <carbon:presentation-file />
129
- </RouterLink>
130
- <RouterLink v-if="__SLIDEV_FEATURE_PRESENTER__ && isPresenterAvailable" :to="presenterLink" class="slidev-icon-btn" title="Presenter Mode">
124
+ </IconButton>
125
+ <IconButton v-if="__SLIDEV_FEATURE_PRESENTER__ && isPresenterAvailable" title="Presenter Mode" @click="enterPresenter">
131
126
  <carbon:user-speaker />
132
- </RouterLink>
127
+ </IconButton>
133
128
 
134
129
  <IconButton
135
130
  v-if="__DEV__ && __SLIDEV_FEATURE_EDITOR__"
@@ -2,7 +2,7 @@
2
2
  import { provideLocal, useElementSize, useStyleTag } from '@vueuse/core'
3
3
  import { computed, ref, watchEffect } from 'vue'
4
4
  import { configs, slideAspect, slideHeight, slideWidth } from '../env'
5
- import { injectionSlideScale } from '../constants'
5
+ import { injectionSlideElement, injectionSlideScale } from '../constants'
6
6
  import { useNav } from '../composables/useNav'
7
7
 
8
8
  const props = defineProps({
@@ -23,11 +23,12 @@ const props = defineProps({
23
23
 
24
24
  const { clicksDirection, isPrintMode } = useNav()
25
25
 
26
- const root = ref<HTMLDivElement>()
27
- const element = useElementSize(root)
26
+ const root = ref<HTMLDivElement | null>(null)
27
+ const rootSize = useElementSize(root)
28
+ const slideElement = ref<HTMLElement | null>(null)
28
29
 
29
- const width = computed(() => props.width ? props.width : element.width.value)
30
- const height = computed(() => props.width ? props.width / slideAspect.value : element.height.value)
30
+ const width = computed(() => props.width ? props.width : rootSize.width.value)
31
+ const height = computed(() => props.width ? props.width / slideAspect.value : rootSize.height.value)
31
32
 
32
33
  if (props.width) {
33
34
  watchEffect(() => {
@@ -42,7 +43,7 @@ const screenAspect = computed(() => width.value / height.value)
42
43
 
43
44
  const scale = computed(() => {
44
45
  if (props.scale && !isPrintMode.value)
45
- return props.scale
46
+ return +props.scale
46
47
  if (screenAspect.value < slideAspect.value)
47
48
  return width.value / slideWidth.value
48
49
  return height.value * slideAspect.value / slideWidth.value
@@ -69,12 +70,13 @@ if (props.isMain) {
69
70
  `))
70
71
  }
71
72
 
72
- provideLocal(injectionSlideScale, scale as any)
73
+ provideLocal(injectionSlideScale, scale)
74
+ provideLocal(injectionSlideElement, slideElement)
73
75
  </script>
74
76
 
75
77
  <template>
76
78
  <div id="slide-container" ref="root" class="slidev-slides-container" :class="className">
77
- <div id="slide-content" class="slidev-slide-content" :style="style">
79
+ <div id="slide-content" ref="slideElement" class="slidev-slide-content" :style="style">
78
80
  <slot />
79
81
  </div>
80
82
  <slot name="controls" />
@@ -4,6 +4,7 @@ import type { PropType } from 'vue'
4
4
  import { provideLocal } from '@vueuse/core'
5
5
  import type { ClicksContext, RenderContext, SlideRoute } from '@slidev/types'
6
6
  import { injectionActive, injectionClicksContext, injectionCurrentPage, injectionRenderContext, injectionRoute, injectionSlideZoom } from '../constants'
7
+ import { getSlideClass } from '../utils'
7
8
  import SlideLoading from './SlideLoading.vue'
8
9
 
9
10
  const props = defineProps({
@@ -67,16 +68,24 @@ const SlideComponent = defineAsyncComponent({
67
68
  </script>
68
69
 
69
70
  <template>
70
- <component
71
- :is="SlideComponent"
72
- :style="style"
73
- :data-slidev-no="props.route.no"
74
- :class="{ 'disable-view-transition': !['slide', 'presenter'].includes(props.renderContext) }"
75
- />
71
+ <div :class="getSlideClass(route)">
72
+ <component
73
+ :is="SlideComponent"
74
+ :style="style"
75
+ :data-slidev-no="props.route.no"
76
+ :class="{ 'disable-view-transition': !['slide', 'presenter'].includes(props.renderContext) }"
77
+ />
78
+ </div>
76
79
  </template>
77
80
 
78
81
  <style scoped>
79
82
  .disable-view-transition:deep(*) {
80
83
  view-transition-name: none !important;
81
84
  }
85
+
86
+ .slidev-page {
87
+ position: absolute;
88
+ width: 100%;
89
+ height: 100%;
90
+ }
82
91
  </style>
@@ -2,13 +2,14 @@
2
2
  import { TransitionGroup, computed, shallowRef, watch } from 'vue'
3
3
  import { recomputeAllPoppers } from 'floating-vue'
4
4
  import { useNav } from '../composables/useNav'
5
- import { getSlideClass } from '../utils'
6
5
  import { useViewTransition } from '../composables/useViewTransition'
7
6
  import { skipTransition } from '../logic/hmr'
8
7
  import { createFixedClicks } from '../composables/useClicks'
8
+ import { activeDragElement } from '../state'
9
9
  import { CLICKS_MAX } from '../constants'
10
10
  import SlideWrapper from './SlideWrapper.vue'
11
11
  import PresenterMouse from './PresenterMouse.vue'
12
+ import DragControl from './DragControl.vue'
12
13
 
13
14
  import GlobalTop from '#slidev/global-components/top'
14
15
  import GlobalBottom from '#slidev/global-components/bottom'
@@ -65,21 +66,19 @@ function onAfterLeave() {
65
66
  tag="div"
66
67
  @after-leave="onAfterLeave"
67
68
  >
68
- <div
69
+ <SlideWrapper
70
+ :is="route.component!"
69
71
  v-for="route of loadedRoutes"
70
72
  v-show="route === currentSlideRoute"
71
73
  :key="route.no"
72
- >
73
- <SlideWrapper
74
- :is="route.component!"
75
- :clicks-context="isPrintMode && !isPrintWithClicks ? createFixedClicks(route, CLICKS_MAX) : getPrimaryClicks(route)"
76
- :class="getSlideClass(route)"
77
- :route="route"
78
- :render-context="renderContext"
79
- />
80
- </div>
74
+ :clicks-context="isPrintMode && !isPrintWithClicks ? createFixedClicks(route, CLICKS_MAX) : getPrimaryClicks(route)"
75
+ :route="route"
76
+ :render-context="renderContext"
77
+ />
81
78
  </component>
82
79
 
80
+ <DragControl v-if="activeDragElement" :data="activeDragElement" />
81
+
83
82
  <div id="twoslash-container" />
84
83
 
85
84
  <!-- Global Top -->
@@ -95,10 +94,4 @@ function onAfterLeave() {
95
94
  #slideshow {
96
95
  height: 100%;
97
96
  }
98
-
99
- #slideshow > div {
100
- position: absolute;
101
- height: 100%;
102
- width: 100%;
103
- }
104
97
  </style>
@@ -0,0 +1,34 @@
1
+ import type { ContextMenuItem } from '@slidev/types'
2
+ import type { ComputedRef } from 'vue'
3
+ import { shallowRef } from 'vue'
4
+ import setupContextMenu from '../setup/context-menu'
5
+ import { configs, mode } from '../env'
6
+
7
+ export const currentContextMenu = shallowRef<null | {
8
+ x: number
9
+ y: number
10
+ items: ComputedRef<ContextMenuItem[]>
11
+ }>(null)
12
+
13
+ export function openContextMenu(x: number, y: number) {
14
+ currentContextMenu.value = {
15
+ x,
16
+ y,
17
+ items: setupContextMenu(),
18
+ }
19
+ }
20
+
21
+ export function closeContextMenu() {
22
+ currentContextMenu.value = null
23
+ }
24
+
25
+ export function onContextMenu(ev: MouseEvent) {
26
+ if (configs.contextMenu !== true && configs.contextMenu !== undefined && configs.contextMenu !== mode)
27
+ return
28
+ if (ev.shiftKey || ev.defaultPrevented)
29
+ return
30
+
31
+ openContextMenu(ev.pageX, ev.pageY)
32
+ ev.preventDefault()
33
+ ev.stopPropagation()
34
+ }
package/logic/utils.ts CHANGED
@@ -32,24 +32,6 @@ export function makeId(length = 5) {
32
32
  return result.join('')
33
33
  }
34
34
 
35
- /**
36
- * '+3' => '+3'
37
- * '-3' => '-3'
38
- * '3' => 3
39
- * 3 => 3
40
- */
41
- export function normalizeAtProp(at: string | number = '+1'): [isRelative: boolean, value: number] {
42
- let n = +at
43
- if (Number.isNaN(n)) {
44
- console.warn('[slidev] Invalid click position:', at)
45
- n = 0
46
- }
47
- return [
48
- typeof at === 'string' && '+-'.includes(at[0]),
49
- n,
50
- ]
51
- }
52
-
53
35
  export function updateCodeHighlightRange(
54
36
  rangeStr: string,
55
37
  linesCount: number,