@slidev/client 0.48.8 → 0.49.0-beta.1
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/builtin/ShikiMagicMove.vue +34 -25
- package/builtin/SlidevVideo.vue +54 -62
- package/builtin/VDrag.vue +27 -0
- package/composables/useDragElements.ts +282 -0
- package/composables/useSlideBounds.ts +30 -0
- package/composables/useSlideInfo.ts +12 -5
- package/constants.ts +3 -0
- package/internals/DragControl.vue +396 -0
- package/internals/SlideContainer.vue +10 -8
- package/internals/SlideWrapper.vue +15 -6
- package/internals/SlidesShow.vue +10 -18
- package/modules/v-click.ts +40 -21
- package/modules/v-drag.ts +44 -0
- package/modules/v-mark.ts +7 -1
- package/modules/v-motion.ts +120 -0
- package/package.json +13 -13
- package/setup/code-runners.ts +94 -10
- package/setup/main.ts +4 -2
- package/setup/shortcuts.ts +7 -5
- package/state/index.ts +4 -1
- package/utils.ts +16 -0
|
@@ -0,0 +1,396 @@
|
|
|
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
|
+
:data-drag-id="id"
|
|
359
|
+
:style="{
|
|
360
|
+
position: 'absolute',
|
|
361
|
+
zIndex: 100,
|
|
362
|
+
left: `${zoom * (x0 - width / 2)}px`,
|
|
363
|
+
top: `${zoom * (y0 - height / 2)}px`,
|
|
364
|
+
width: `${zoom * width}px`,
|
|
365
|
+
height: `${zoom * height}px`,
|
|
366
|
+
transformOrigin: 'center center',
|
|
367
|
+
transform: `rotate(${rotate}deg)`,
|
|
368
|
+
}"
|
|
369
|
+
@pointerdown="onPointerdown"
|
|
370
|
+
@pointermove="onPointermove"
|
|
371
|
+
@pointerup="onPointerup"
|
|
372
|
+
>
|
|
373
|
+
<div class="absolute inset-0 z-100 b b-dark dark:b-gray-400">
|
|
374
|
+
<template v-if="!autoHeight">
|
|
375
|
+
<div v-bind="getCornerProps(true, true)" />
|
|
376
|
+
<div v-bind="getCornerProps(true, false)" />
|
|
377
|
+
<div v-bind="getCornerProps(false, true)" />
|
|
378
|
+
<div v-bind="getCornerProps(false, false)" />
|
|
379
|
+
</template>
|
|
380
|
+
<div v-bind="getBorderProps('l')" />
|
|
381
|
+
<div v-bind="getBorderProps('r')" />
|
|
382
|
+
<template v-if="!autoHeight">
|
|
383
|
+
<div v-bind="getBorderProps('t')" />
|
|
384
|
+
<div v-bind="getBorderProps('b')" />
|
|
385
|
+
</template>
|
|
386
|
+
<div v-bind="getRotateProps()" />
|
|
387
|
+
<div
|
|
388
|
+
class="absolute -top-15px w-0 b b-dashed b-dark dark:b-gray-400"
|
|
389
|
+
:style="{
|
|
390
|
+
left: 'calc(50% - 1px)',
|
|
391
|
+
height: autoHeight ? '14px' : '10px',
|
|
392
|
+
}"
|
|
393
|
+
/>
|
|
394
|
+
</div>
|
|
395
|
+
</div>
|
|
396
|
+
</template>
|
|
@@ -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
|
|
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 :
|
|
30
|
-
const height = computed(() => props.width ? props.width / slideAspect.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
|
|
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
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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>
|
package/internals/SlidesShow.vue
CHANGED
|
@@ -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,22 +66,19 @@ function onAfterLeave() {
|
|
|
65
66
|
tag="div"
|
|
66
67
|
@after-leave="onAfterLeave"
|
|
67
68
|
>
|
|
68
|
-
<
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
:class="getSlideClass(route)"
|
|
77
|
-
:route="route"
|
|
78
|
-
:render-context="renderContext"
|
|
79
|
-
class="overflow-hidden"
|
|
80
|
-
/>
|
|
81
|
-
</div>
|
|
74
|
+
:clicks-context="isPrintMode && !isPrintWithClicks ? createFixedClicks(route, CLICKS_MAX) : getPrimaryClicks(route)"
|
|
75
|
+
:route="route"
|
|
76
|
+
:render-context="renderContext"
|
|
77
|
+
/>
|
|
82
78
|
</component>
|
|
83
79
|
|
|
80
|
+
<DragControl v-if="activeDragElement" :data="activeDragElement" />
|
|
81
|
+
|
|
84
82
|
<div id="twoslash-container" />
|
|
85
83
|
|
|
86
84
|
<!-- Global Top -->
|
|
@@ -96,10 +94,4 @@ function onAfterLeave() {
|
|
|
96
94
|
#slideshow {
|
|
97
95
|
height: 100%;
|
|
98
96
|
}
|
|
99
|
-
|
|
100
|
-
#slideshow > div {
|
|
101
|
-
position: absolute;
|
|
102
|
-
height: 100%;
|
|
103
|
-
width: 100%;
|
|
104
|
-
}
|
|
105
97
|
</style>
|
package/modules/v-click.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ResolvedClicksInfo } from '@slidev/types'
|
|
2
|
-
import type { App, DirectiveBinding
|
|
2
|
+
import type { App, DirectiveBinding } from 'vue'
|
|
3
3
|
import { computed, watchEffect } from 'vue'
|
|
4
4
|
import {
|
|
5
5
|
CLASS_VCLICK_CURRENT,
|
|
@@ -8,14 +8,12 @@ import {
|
|
|
8
8
|
CLASS_VCLICK_HIDDEN_EXP,
|
|
9
9
|
CLASS_VCLICK_PRIOR,
|
|
10
10
|
CLASS_VCLICK_TARGET,
|
|
11
|
+
injectionClickVisibility,
|
|
11
12
|
injectionClicksContext,
|
|
12
13
|
} from '../constants'
|
|
14
|
+
import { directiveInject, directiveProvide } from '../utils'
|
|
13
15
|
|
|
14
|
-
export type VClickValue = string | [string | number, string | number] | boolean
|
|
15
|
-
|
|
16
|
-
export function dirInject<T = unknown>(dir: DirectiveBinding<any>, key: InjectionKey<T> | string, defaultValue?: T): T | undefined {
|
|
17
|
-
return (dir.instance?.$ as any).provides[key as any] ?? defaultValue
|
|
18
|
-
}
|
|
16
|
+
export type VClickValue = undefined | string | number | [string | number, string | number] | boolean
|
|
19
17
|
|
|
20
18
|
export function createVClickDirectives() {
|
|
21
19
|
return {
|
|
@@ -25,7 +23,7 @@ export function createVClickDirectives() {
|
|
|
25
23
|
name: 'v-click',
|
|
26
24
|
|
|
27
25
|
mounted(el, dir) {
|
|
28
|
-
const resolved = resolveClick(el, dir, dir.value)
|
|
26
|
+
const resolved = resolveClick(el, dir, dir.value, true)
|
|
29
27
|
if (resolved == null)
|
|
30
28
|
return
|
|
31
29
|
|
|
@@ -37,7 +35,8 @@ export function createVClickDirectives() {
|
|
|
37
35
|
if (clicks[1] != null)
|
|
38
36
|
el.dataset.slidevClicksEnd = String(clicks[1])
|
|
39
37
|
|
|
40
|
-
|
|
38
|
+
// @ts-expect-error extra prop
|
|
39
|
+
el.watchStopHandle = watchEffect(() => {
|
|
41
40
|
const active = resolved.isActive.value
|
|
42
41
|
const current = resolved.isCurrent.value
|
|
43
42
|
const prior = active && !current
|
|
@@ -62,13 +61,14 @@ export function createVClickDirectives() {
|
|
|
62
61
|
name: 'v-after',
|
|
63
62
|
|
|
64
63
|
mounted(el, dir) {
|
|
65
|
-
const resolved = resolveClick(el, dir, dir.value, true)
|
|
64
|
+
const resolved = resolveClick(el, dir, dir.value, true, true)
|
|
66
65
|
if (resolved == null)
|
|
67
66
|
return
|
|
68
67
|
|
|
69
68
|
el.classList.toggle(CLASS_VCLICK_TARGET, true)
|
|
70
69
|
|
|
71
|
-
|
|
70
|
+
// @ts-expect-error extra prop
|
|
71
|
+
el.watchStopHandle = watchEffect(() => {
|
|
72
72
|
const active = resolved.isActive.value
|
|
73
73
|
const current = resolved.isCurrent.value
|
|
74
74
|
const prior = active && !current
|
|
@@ -93,13 +93,14 @@ export function createVClickDirectives() {
|
|
|
93
93
|
name: 'v-click-hide',
|
|
94
94
|
|
|
95
95
|
mounted(el, dir) {
|
|
96
|
-
const resolved = resolveClick(el, dir, dir.value, false, true)
|
|
96
|
+
const resolved = resolveClick(el, dir, dir.value, true, false, true)
|
|
97
97
|
if (resolved == null)
|
|
98
98
|
return
|
|
99
99
|
|
|
100
100
|
el.classList.toggle(CLASS_VCLICK_TARGET, true)
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
// @ts-expect-error extra prop
|
|
103
|
+
el.watchStopHandle = watchEffect(() => {
|
|
103
104
|
const active = resolved.isActive.value
|
|
104
105
|
const current = resolved.isCurrent.value
|
|
105
106
|
const prior = active && !current
|
|
@@ -117,20 +118,20 @@ export function createVClickDirectives() {
|
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
function
|
|
121
|
+
function isClickActive(thisClick: number | [number, number], clicks: number) {
|
|
121
122
|
return Array.isArray(thisClick)
|
|
122
123
|
? thisClick[0] <= clicks && clicks < thisClick[1]
|
|
123
124
|
: thisClick <= clicks
|
|
124
125
|
}
|
|
125
126
|
|
|
126
|
-
function
|
|
127
|
+
function isClickCurrent(thisClick: number | [number, number], clicks: number) {
|
|
127
128
|
return Array.isArray(thisClick)
|
|
128
129
|
? thisClick[0] === clicks
|
|
129
130
|
: thisClick === clicks
|
|
130
131
|
}
|
|
131
132
|
|
|
132
|
-
export function resolveClick(el: Element, dir: DirectiveBinding<any>, value: VClickValue, clickAfter = false, flagHide = false): ResolvedClicksInfo | null {
|
|
133
|
-
const ctx =
|
|
133
|
+
export function resolveClick(el: Element | string, dir: DirectiveBinding<any>, value: VClickValue, provideVisibility = false, clickAfter = false, flagHide = false): ResolvedClicksInfo | null {
|
|
134
|
+
const ctx = directiveInject(dir, injectionClicksContext)?.value
|
|
134
135
|
|
|
135
136
|
if (!el || !ctx)
|
|
136
137
|
return null
|
|
@@ -152,29 +153,47 @@ export function resolveClick(el: Element, dir: DirectiveBinding<any>, value: VCl
|
|
|
152
153
|
if (Array.isArray(value)) {
|
|
153
154
|
// range (absolute)
|
|
154
155
|
delta = 0
|
|
155
|
-
thisClick = value
|
|
156
|
+
thisClick = [+value[0], +value[1]]
|
|
156
157
|
maxClick = +value[1]
|
|
157
158
|
}
|
|
158
159
|
else {
|
|
159
160
|
({ start: thisClick, end: maxClick, delta } = ctx.resolve(value))
|
|
160
161
|
}
|
|
161
162
|
|
|
163
|
+
const isActive = computed(() => isClickActive(thisClick, ctx.current))
|
|
164
|
+
const isCurrent = computed(() => isClickCurrent(thisClick, ctx.current))
|
|
165
|
+
const isShown = computed(() => flagHide ? !isActive.value : isActive.value)
|
|
166
|
+
|
|
162
167
|
const resolved: ResolvedClicksInfo = {
|
|
163
168
|
max: maxClick,
|
|
164
169
|
clicks: thisClick,
|
|
165
170
|
delta,
|
|
166
|
-
isActive
|
|
167
|
-
isCurrent
|
|
168
|
-
isShown
|
|
171
|
+
isActive,
|
|
172
|
+
isCurrent,
|
|
173
|
+
isShown,
|
|
169
174
|
flagFade,
|
|
170
175
|
flagHide,
|
|
171
176
|
}
|
|
172
177
|
ctx.register(el, resolved)
|
|
178
|
+
|
|
179
|
+
if (provideVisibility) {
|
|
180
|
+
directiveProvide(dir, injectionClickVisibility, computed(() => {
|
|
181
|
+
if (isShown.value)
|
|
182
|
+
return true
|
|
183
|
+
if (Array.isArray(thisClick))
|
|
184
|
+
return ctx.current < thisClick[0] ? 'before' : 'after'
|
|
185
|
+
else
|
|
186
|
+
return flagHide ? 'after' : 'before'
|
|
187
|
+
}))
|
|
188
|
+
}
|
|
189
|
+
|
|
173
190
|
return resolved
|
|
174
191
|
}
|
|
175
192
|
|
|
176
193
|
function unmounted(el: HTMLElement, dir: DirectiveBinding<any>) {
|
|
177
194
|
el.classList.toggle(CLASS_VCLICK_TARGET, false)
|
|
178
|
-
const ctx =
|
|
195
|
+
const ctx = directiveInject(dir, injectionClicksContext)?.value
|
|
179
196
|
ctx?.unregister(el)
|
|
197
|
+
// @ts-expect-error extra prop
|
|
198
|
+
el.watchStopHandle?.()
|
|
180
199
|
}
|