@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.
@@ -45,43 +45,45 @@ onMounted(() => {
45
45
  // Calculate the step and rangeStr based on the current click count
46
46
  const clickCount = clicks.current - start
47
47
  let step = steps.length - 1
48
- let _currentClickSum = 0
48
+ let currentClickSum = 0
49
49
  let rangeStr = 'all'
50
50
  for (let i = 0; i < ranges.value.length; i++) {
51
51
  const current = ranges.value[i]
52
- if (clickCount < _currentClickSum + current.length - 1) {
52
+ if (clickCount < currentClickSum + current.length - 1) {
53
53
  step = i
54
- rangeStr = current[clickCount - _currentClickSum + 1]
54
+ rangeStr = current[clickCount - currentClickSum + 1]
55
55
  break
56
56
  }
57
- _currentClickSum += current.length || 1
57
+ currentClickSum += current.length || 1
58
58
  }
59
59
  stepIndex.value = step
60
60
 
61
- const pre = container.value?.querySelector('.shiki') as HTMLElement
62
- if (!pre)
63
- return
61
+ setTimeout(() => {
62
+ const pre = container.value?.querySelector('.shiki') as HTMLElement
63
+ if (!pre)
64
+ return
64
65
 
65
- const children = (Array.from(pre.children) as HTMLElement[])
66
- .slice(1) // Remove the first anchor
67
- .filter(i => !i.className.includes('shiki-magic-move-leave')) // Filter the leaving elements
66
+ const children = (Array.from(pre.children) as HTMLElement[])
67
+ .slice(1) // Remove the first anchor
68
+ .filter(i => !i.className.includes('shiki-magic-move-leave')) // Filter the leaving elements
68
69
 
69
- // Group to lines between `<br>`
70
- const lines = children.reduce((acc, el) => {
71
- if (el.tagName === 'BR')
72
- acc.push([])
73
- else
74
- acc[acc.length - 1].push(el)
75
- return acc
76
- }, [[]] as HTMLElement[][])
70
+ // Group to lines between `<br>`
71
+ const lines = children.reduce((acc, el) => {
72
+ if (el.tagName === 'BR')
73
+ acc.push([])
74
+ else
75
+ acc[acc.length - 1].push(el)
76
+ return acc
77
+ }, [[]] as HTMLElement[][])
77
78
 
78
- // Update highlight range
79
- updateCodeHighlightRange(
80
- rangeStr,
81
- lines.length,
82
- 1,
83
- no => lines[no],
84
- )
79
+ // Update highlight range
80
+ updateCodeHighlightRange(
81
+ rangeStr,
82
+ lines.length,
83
+ 1,
84
+ no => lines[no],
85
+ )
86
+ })
85
87
  },
86
88
  { immediate: true },
87
89
  )
@@ -104,3 +106,10 @@ onMounted(() => {
104
106
  />
105
107
  </div>
106
108
  </template>
109
+
110
+ <style>
111
+ .slidev-code-magic-move .shiki-magic-move-enter-from,
112
+ .slidev-code-magic-move .shiki-magic-move-leave-to {
113
+ opacity: 0;
114
+ }
115
+ </style>
@@ -1,86 +1,78 @@
1
1
  <script setup lang="ts">
2
- import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
2
+ import { computed, onMounted, ref, watch } from 'vue'
3
+ import { and } from '@vueuse/math'
3
4
  import { useSlideContext } from '../context'
5
+ import { useNav } from '../composables/useNav'
4
6
 
5
7
  const props = defineProps<{
6
- autoPlay?: boolean | 'once' | 'resume' | 'resumeOnce'
7
- autoPause?: 'slide' | 'click'
8
- autoReset?: 'slide' | 'click'
8
+ autoplay?: boolean | 'once'
9
+ autoreset?: 'slide' | 'click'
10
+ poster?: string
11
+ printPoster?: string
12
+ timestamp?: string | number
13
+ printTimestamp?: string | number | 'last'
14
+ controls?: boolean
9
15
  }>()
10
16
 
17
+ const printPoster = computed(() => props.printPoster ?? props.poster)
18
+ const printTimestamp = computed(() => props.printTimestamp ?? props.timestamp ?? 0)
19
+
11
20
  const {
12
21
  $slidev,
13
- $clicksContext: clicks,
14
- $renderContext: currentContext,
15
- $route: route,
22
+ $clicksContext,
23
+ $renderContext,
24
+ $route,
16
25
  } = useSlideContext()
26
+ const { isPrintMode } = useNav()
27
+
28
+ const noPlay = computed(() => isPrintMode.value || !['slide', 'presenter'].includes($renderContext.value))
17
29
 
18
30
  const video = ref<HTMLMediaElement>()
19
31
  const played = ref(false)
20
- const ended = ref(false)
21
-
22
- const matchRoute = computed(() => {
23
- if (!video.value || currentContext?.value !== 'slide')
24
- return false
25
- return route && route.no === $slidev?.nav.currentSlideNo
26
- })
27
-
28
- const matchClick = computed(() => {
29
- if (!video.value || currentContext?.value !== 'slide' || !clicks)
30
- return false
31
- return clicks.map.get(video.value)?.isShown?.value ?? true
32
- })
33
-
34
- const matchRouteAndClick = computed(() => matchRoute.value && matchClick.value)
35
-
36
- watch(matchRouteAndClick, () => {
37
- if (!video.value || currentContext?.value !== 'slide')
38
- return
39
-
40
- if (matchRouteAndClick.value) {
41
- if (props.autoReset === 'click')
42
- video.value.currentTime = 0
43
- if (props.autoPlay && (!played.value || props.autoPlay === 'resume' || (props.autoPlay === 'resumeOnce' && !ended.value)))
44
- video.value.play()
45
- }
46
-
47
- if ((props.autoPause === 'click' && !matchRouteAndClick.value) || (props.autoPause === 'slide' && !matchRoute.value))
48
- video.value.pause()
49
- })
50
32
 
51
- watch(matchRoute, () => {
52
- if (!video.value || currentContext?.value !== 'slide')
33
+ onMounted(() => {
34
+ if (noPlay.value)
53
35
  return
54
36
 
55
- if (matchRoute.value && props.autoReset === 'slide')
56
- video.value.currentTime = 0
37
+ const timestamp = +(props.timestamp ?? 0)
38
+ video.value!.currentTime = timestamp
39
+
40
+ const matchRoute = computed(() => !!$route && $route.no === $slidev?.nav.currentSlideNo)
41
+ const matchClick = computed(() => !!video.value && ($clicksContext.map.get(video.value)?.isShown?.value ?? true))
42
+ const matchRouteAndClick = and(matchRoute, matchClick)
43
+
44
+ watch(matchRouteAndClick, () => {
45
+ if (matchRouteAndClick.value) {
46
+ if (props.autoplay === true || (props.autoplay === 'once' && !played.value))
47
+ video.value!.play()
48
+ }
49
+ else {
50
+ video.value!.pause()
51
+ if (props.autoreset === 'click' || (props.autoreset === 'slide' && !matchRoute.value))
52
+ video.value!.currentTime = timestamp
53
+ }
54
+ }, { immediate: true })
57
55
  })
58
56
 
59
- function onPlay() {
60
- played.value = true
61
- }
62
-
63
- function onEnded() {
64
- ended.value = true
57
+ function onLoadedMetadata(ev: Event) {
58
+ // The video may be loaded before component mounted
59
+ const element = ev.target as HTMLMediaElement
60
+ if (noPlay.value && (!printPoster.value || props.printTimestamp)) {
61
+ element.currentTime = printTimestamp.value === 'last'
62
+ ? element.duration
63
+ : +printTimestamp.value
64
+ }
65
65
  }
66
-
67
- onMounted(() => {
68
- if (!video.value || currentContext?.value !== 'slide')
69
- return
70
- video.value?.addEventListener('play', onPlay)
71
- video.value?.addEventListener('ended', onEnded)
72
- })
73
-
74
- onUnmounted(() => {
75
- if (!video.value || currentContext?.value !== 'slide')
76
- return
77
- video.value?.removeEventListener('play', onPlay)
78
- video.value?.removeEventListener('ended', onEnded)
79
- })
80
66
  </script>
81
67
 
82
68
  <template>
83
- <video ref="video">
69
+ <video
70
+ ref="video"
71
+ :poster="noPlay ? printPoster : props.poster"
72
+ :controls="!noPlay && props.controls"
73
+ @play="played = true"
74
+ @loadedmetadata="onLoadedMetadata"
75
+ >
84
76
  <slot />
85
77
  </video>
86
78
  </template>
@@ -0,0 +1,27 @@
1
+ <script setup lang="ts">
2
+ import { onMounted, onUnmounted } from 'vue'
3
+ import type { DragElementMarkdownSource } from '../composables/useDragElements'
4
+ import { useDragElement } from '../composables/useDragElements'
5
+
6
+ const props = defineProps<{
7
+ pos?: string
8
+ markdownSource?: DragElementMarkdownSource
9
+ }>()
10
+
11
+ const { id, container, containerStyle, mounted, unmounted, startDragging } = useDragElement(null, props.pos, props.markdownSource)
12
+
13
+ onMounted(mounted)
14
+ onUnmounted(unmounted)
15
+ </script>
16
+
17
+ <template>
18
+ <div
19
+ ref="container"
20
+ :data-drag-id="id"
21
+ :style="containerStyle"
22
+ class="p-1"
23
+ @dblclick="startDragging"
24
+ >
25
+ <slot />
26
+ </div>
27
+ </template>
@@ -0,0 +1,282 @@
1
+ import { debounce, ensureSuffix } from '@antfu/utils'
2
+ import type { SlidePatch } from '@slidev/types'
3
+ import { injectLocal, onClickOutside, useWindowFocus } from '@vueuse/core'
4
+ import type { CSSProperties, DirectiveBinding, InjectionKey, WatchStopHandle } from 'vue'
5
+ import { computed, ref, watch } from 'vue'
6
+ import { injectionCurrentPage, injectionFrontmatter, injectionRenderContext, injectionSlideElement, injectionSlideScale, injectionSlideZoom } from '../constants'
7
+ import { makeId } from '../logic/utils'
8
+ import { activeDragElement } from '../state'
9
+ import { directiveInject } from '../utils'
10
+ import { useSlideBounds } from './useSlideBounds'
11
+ import { useDynamicSlideInfo } from './useSlideInfo'
12
+
13
+ export type DragElementDataSource = 'frontmatter' | 'prop' | 'directive'
14
+ /**
15
+ * Markdown source position, injected by markdown-it plugin
16
+ */
17
+ export type DragElementMarkdownSource = [startLine: number, endLine: number, index: number]
18
+
19
+ export type DragElementsUpdater = (id: string, posStr: string, type: DragElementDataSource, markdownSource?: DragElementMarkdownSource) => void
20
+
21
+ const map: Record<number, DragElementsUpdater> = {}
22
+
23
+ export function useDragElementsUpdater(no: number) {
24
+ if (!(__DEV__ && __SLIDEV_FEATURE_EDITOR__))
25
+ return () => {}
26
+
27
+ if (map[no])
28
+ return map[no]
29
+
30
+ const { info, update } = useDynamicSlideInfo(no)
31
+
32
+ let newPatch: SlidePatch | null = null
33
+ async function save() {
34
+ if (newPatch) {
35
+ await update({
36
+ ...newPatch,
37
+ skipHmr: true,
38
+ })
39
+ newPatch = null
40
+ }
41
+ }
42
+ const debouncedSave = debounce(500, save)
43
+
44
+ return map[no] = (id, posStr, type, markdownSource) => {
45
+ if (!info.value)
46
+ return
47
+
48
+ if (type === 'frontmatter') {
49
+ const frontmatter = info.value.frontmatter
50
+ frontmatter.dragPos ||= {}
51
+ if (frontmatter.dragPos[id] === posStr)
52
+ return
53
+ frontmatter.dragPos[id] = posStr
54
+ newPatch = {
55
+ frontmatter,
56
+ }
57
+ }
58
+ else {
59
+ if (!markdownSource)
60
+ throw new Error(`[Slidev] VDrag Element ${id} is missing markdown source`)
61
+
62
+ const [startLine, endLine, idx] = markdownSource
63
+ const lines = info.value.content.split(/\r?\n/g)
64
+
65
+ let section = lines.slice(startLine, endLine).join('\n')
66
+ let replaced = false
67
+
68
+ section = type === 'prop'
69
+ ? section.replace(/<(v-?drag)(.*?)>/ig, (full, tag, attrs, index) => {
70
+ if (index === idx) {
71
+ replaced = true
72
+ const posMatch = attrs.match(/pos=".*?"/)
73
+ if (!posMatch)
74
+ return `<${tag}${ensureSuffix(' ', attrs)}pos="${posStr}">`
75
+ const start = posMatch.index
76
+ const end = start + posMatch[0].length
77
+ return `<${tag}${attrs.slice(0, start)}pos="${posStr}"${attrs.slice(end)}>`
78
+ }
79
+ return full
80
+ })
81
+ : section.replace(/(?<![</\w])v-drag(?:=".*?")?/ig, (full, index) => {
82
+ if (index === idx) {
83
+ replaced = true
84
+ return `v-drag="${posStr}"`
85
+ }
86
+ return full
87
+ })
88
+
89
+ if (!replaced)
90
+ throw new Error(`[Slidev] VDrag Element ${id} is not found in the markdown source`)
91
+
92
+ lines.splice(
93
+ startLine,
94
+ endLine - startLine,
95
+ section,
96
+ )
97
+
98
+ const newContent = lines.join('\n')
99
+ if (info.value.content === newContent)
100
+ return
101
+ newPatch = {
102
+ content: newContent,
103
+ }
104
+ info.value = {
105
+ ...info.value,
106
+ content: newContent,
107
+ }
108
+ }
109
+ debouncedSave()
110
+ }
111
+ }
112
+
113
+ export function useDragElement(directive: DirectiveBinding | null, posRaw?: string | number | number[], markdownSource?: DragElementMarkdownSource) {
114
+ function inject<T>(key: InjectionKey<T> | string): T | undefined {
115
+ return directive
116
+ ? directiveInject(directive, key)
117
+ : injectLocal(key)
118
+ }
119
+
120
+ const renderContext = inject(injectionRenderContext)!
121
+ const frontmatter = inject(injectionFrontmatter) ?? {}
122
+ const page = inject(injectionCurrentPage)!
123
+ const updater = computed(() => useDragElementsUpdater(page.value))
124
+ const scale = inject(injectionSlideScale) ?? ref(1)
125
+ const zoom = inject(injectionSlideZoom) ?? ref(1)
126
+ const { left: slideLeft, top: slideTop, stop: stopWatchBounds } = useSlideBounds(inject(injectionSlideElement) ?? ref())
127
+ const enabled = ['slide', 'presenter'].includes(renderContext.value)
128
+
129
+ let dataSource: DragElementDataSource = directive ? 'directive' : 'prop'
130
+ let id: string = makeId()
131
+ let pos: number[] | undefined
132
+ if (Array.isArray(posRaw)) {
133
+ pos = posRaw
134
+ }
135
+ else if (typeof posRaw === 'string' && posRaw.includes(',')) {
136
+ pos = posRaw.split(',').map(Number)
137
+ }
138
+ else if (posRaw != null) {
139
+ dataSource = 'frontmatter'
140
+ id = `${posRaw}`
141
+ posRaw = frontmatter?.dragPos?.[id]
142
+ pos = (posRaw as string)?.split(',').map(Number)
143
+ }
144
+
145
+ if (dataSource !== 'frontmatter' && !markdownSource)
146
+ throw new Error('[Slidev] Can not identify the source position of the v-drag element, please provide an explicit `id` prop.')
147
+
148
+ const watchStopHandles: WatchStopHandle[] = [stopWatchBounds]
149
+
150
+ const autoHeight = posRaw != null && !Number.isFinite(pos?.[3])
151
+ pos ??= [Number.NaN, Number.NaN, 0]
152
+ const width = ref(pos[2])
153
+ const x0 = ref(pos[0] + pos[2] / 2)
154
+
155
+ const rotate = ref(pos[4] ?? 0)
156
+ const rotateRad = computed(() => rotate.value * Math.PI / 180)
157
+ const rotateSin = computed(() => Math.sin(rotateRad.value))
158
+ const rotateCos = computed(() => Math.cos(rotateRad.value))
159
+
160
+ const container = ref<HTMLElement>()
161
+ const bounds = ref({ left: 0, top: 0, width: 0, height: 0 })
162
+ const actualHeight = ref(0)
163
+ function updateBounds() {
164
+ const rect = container.value!.getBoundingClientRect()
165
+ bounds.value = {
166
+ left: rect.left / zoom.value,
167
+ top: rect.top / zoom.value,
168
+ width: rect.width / zoom.value,
169
+ height: rect.height / zoom.value,
170
+ }
171
+ actualHeight.value = ((bounds.value.width + bounds.value.height) / scale.value / (Math.abs(rotateSin.value) + Math.abs(rotateCos.value)) - width.value)
172
+ }
173
+ watchStopHandles.push(watch(width, updateBounds, { flush: 'post' }))
174
+
175
+ const configuredHeight = ref(pos[3] ?? 0)
176
+ const height = computed({
177
+ get: () => (autoHeight ? actualHeight.value : configuredHeight.value) || 0,
178
+ set: v => !autoHeight && (configuredHeight.value = v),
179
+ })
180
+ const configuredY0 = ref(pos[1])
181
+ const y0 = computed({
182
+ get: () => configuredY0.value + height.value / 2,
183
+ set: v => configuredY0.value = v - height.value / 2,
184
+ })
185
+
186
+ const containerStyle = computed<CSSProperties>(() => {
187
+ return Number.isFinite(x0.value)
188
+ ? {
189
+ position: 'absolute',
190
+ zIndex: 100,
191
+ left: `${x0.value - width.value / 2}px`,
192
+ top: `${y0.value - height.value / 2}px`,
193
+ width: `${width.value}px`,
194
+ height: autoHeight ? undefined : `${height.value}px`,
195
+ transformOrigin: 'center center',
196
+ transform: `rotate(${rotate.value}deg)`,
197
+ }
198
+ : {
199
+ position: 'absolute',
200
+ zIndex: 100,
201
+ }
202
+ })
203
+
204
+ watchStopHandles.push(
205
+ watch(
206
+ [x0, y0, width, height, rotate],
207
+ ([x0, y0, w, h, r]) => {
208
+ let posStr = [x0 - w / 2, y0 - h / 2, w].map(Math.round).join()
209
+ if (autoHeight)
210
+ posStr += dataSource === 'directive' ? ',NaN' : ',_'
211
+ else
212
+ posStr += `,${Math.round(h)}`
213
+ if (Math.round(r) !== 0)
214
+ posStr += `,${Math.round(r)}`
215
+
216
+ if (dataSource === 'directive')
217
+ posStr = `[${posStr}]`
218
+
219
+ updater.value(id, posStr, dataSource, markdownSource)
220
+ },
221
+ ),
222
+ )
223
+
224
+ const state = {
225
+ id,
226
+ dataSource,
227
+ markdownSource,
228
+ zoom,
229
+ autoHeight,
230
+ x0,
231
+ y0,
232
+ width,
233
+ height,
234
+ rotate,
235
+ container,
236
+ containerStyle,
237
+ watchStopHandles,
238
+ dragging: computed((): boolean => activeDragElement.value === state),
239
+ mounted() {
240
+ if (!enabled)
241
+ return
242
+ updateBounds()
243
+ if (!posRaw) {
244
+ setTimeout(() => {
245
+ updateBounds()
246
+ x0.value = (bounds.value.left + bounds.value.width / 2 - slideLeft.value) / scale.value
247
+ y0.value = (bounds.value.top - slideTop.value) / scale.value
248
+ width.value = bounds.value.width / scale.value
249
+ height.value = bounds.value.height / scale.value
250
+ }, 100)
251
+ }
252
+ },
253
+ unmounted() {
254
+ if (!enabled)
255
+ return
256
+ state.stopDragging()
257
+ },
258
+ startDragging(): void {
259
+ updateBounds()
260
+ activeDragElement.value = state
261
+ },
262
+ stopDragging(): void {
263
+ if (activeDragElement.value === state)
264
+ activeDragElement.value = null
265
+ },
266
+ }
267
+
268
+ watchStopHandles.push(
269
+ onClickOutside(container, (ev) => {
270
+ if ((ev.target as HTMLElement | null)?.dataset?.dragId !== id)
271
+ state.stopDragging()
272
+ }),
273
+ watch(useWindowFocus(), (focused) => {
274
+ if (!focused)
275
+ state.stopDragging()
276
+ }),
277
+ )
278
+
279
+ return state
280
+ }
281
+
282
+ export type DragElementState = ReturnType<typeof useDragElement>
@@ -0,0 +1,30 @@
1
+ import { useElementBounding } from '@vueuse/core'
2
+ import { inject, ref, watch } from 'vue'
3
+ import { injectionSlideElement } from '../constants'
4
+ import { editorHeight, editorWidth, isEditorVertical, showEditor, slideScale, windowSize } from '../state'
5
+
6
+ export function useSlideBounds(slideElement = inject(injectionSlideElement, ref())) {
7
+ const bounding = useElementBounding(slideElement)
8
+ const stop = watch(
9
+ [
10
+ showEditor,
11
+ isEditorVertical,
12
+ editorWidth,
13
+ editorHeight,
14
+ slideScale,
15
+ windowSize.width,
16
+ windowSize.height,
17
+ ],
18
+ () => {
19
+ setTimeout(bounding.update, 300)
20
+ },
21
+ {
22
+ flush: 'post',
23
+ immediate: true,
24
+ },
25
+ )
26
+ return {
27
+ ...bounding,
28
+ stop,
29
+ }
30
+ }
@@ -6,19 +6,19 @@ import type { SlideInfo, SlidePatch } from '@slidev/types'
6
6
  import { getSlide } from '../logic/slides'
7
7
 
8
8
  export interface UseSlideInfo {
9
- info: Ref<SlideInfo | undefined>
9
+ info: Ref<SlideInfo | null>
10
10
  update: (data: SlidePatch) => Promise<SlideInfo | void>
11
11
  }
12
12
 
13
13
  export function useSlideInfo(no: number): UseSlideInfo {
14
14
  if (!__SLIDEV_HAS_SERVER__) {
15
15
  return {
16
- info: ref(getSlide(no)?.meta.slide) as Ref<SlideInfo | undefined>,
16
+ info: ref(getSlide(no)?.meta.slide ?? null) as Ref<SlideInfo | null>,
17
17
  update: async () => {},
18
18
  }
19
19
  }
20
20
  const url = `/@slidev/slide/${no}.json`
21
- const { data: info, execute } = useFetch(url).json().get()
21
+ const { data: info, execute } = useFetch(url).json<SlideInfo>().get()
22
22
 
23
23
  execute()
24
24
 
@@ -42,7 +42,7 @@ export function useSlideInfo(no: number): UseSlideInfo {
42
42
  info.value = payload.data
43
43
  })
44
44
  import.meta.hot?.on('slidev:update-note', (payload) => {
45
- if (payload.no === no && info.value.note?.trim() !== payload.note?.trim())
45
+ if (payload.no === no && info.value && info.value.note?.trim() !== payload.note?.trim())
46
46
  info.value = { ...info.value, ...payload }
47
47
  })
48
48
  }
@@ -61,7 +61,14 @@ export function useDynamicSlideInfo(no: MaybeRef<number>) {
61
61
  }
62
62
 
63
63
  return {
64
- info: computed(() => get(unref(no)).info.value),
64
+ info: computed({
65
+ get() {
66
+ return get(unref(no)).info.value
67
+ },
68
+ set(newInfo) {
69
+ get(unref(no)).info.value = newInfo
70
+ },
71
+ }),
65
72
  update: async (data: SlidePatch, newId?: number) => {
66
73
  const info = get(newId ?? unref(no))
67
74
  const newData = await info.update(data)
package/constants.ts CHANGED
@@ -6,6 +6,7 @@ import type { SlidevContext } from './modules/context'
6
6
  // The value of the injections keys are implementation details, you should always use them with the reference to the constant instead of the value
7
7
  export const injectionClicksContext = '$$slidev-clicks-context' as unknown as InjectionKey<Ref<ClicksContext>>
8
8
  export const injectionCurrentPage = '$$slidev-page' as unknown as InjectionKey<Ref<number>>
9
+ export const injectionSlideElement = '$$slidev-slide-element' as unknown as InjectionKey<Ref<HTMLElement | null>>
9
10
  export const injectionSlideScale = '$$slidev-slide-scale' as unknown as InjectionKey<ComputedRef<number>>
10
11
  export const injectionSlidevContext = '$$slidev-context' as unknown as InjectionKey<UnwrapNestedRefs<SlidevContext>>
11
12
  export const injectionRoute = '$$slidev-route' as unknown as InjectionKey<SlideRoute>
@@ -13,6 +14,7 @@ export const injectionRenderContext = '$$slidev-render-context' as unknown as In
13
14
  export const injectionActive = '$$slidev-active' as unknown as InjectionKey<Ref<boolean>>
14
15
  export const injectionFrontmatter = '$$slidev-fontmatter' as unknown as InjectionKey<Record<string, any>>
15
16
  export const injectionSlideZoom = '$$slidev-slide-zoom' as unknown as InjectionKey<ComputedRef<number>>
17
+ export const injectionClickVisibility = '$$slidev-click-visibility' as unknown as InjectionKey<ComputedRef<true | 'before' | 'after'>>
16
18
 
17
19
  export const CLASS_VCLICK_TARGET = 'slidev-vclick-target'
18
20
  export const CLASS_VCLICK_HIDDEN = 'slidev-vclick-hidden'
@@ -43,6 +45,7 @@ export const FRONTMATTER_FIELDS = [
43
45
  'title',
44
46
  'transition',
45
47
  'zoom',
48
+ 'dragPos',
46
49
  ]
47
50
 
48
51
  export const HEADMATTER_FIELDS = [