@slidev/client 0.50.0 → 0.51.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.
@@ -1,40 +1,114 @@
1
1
  <script setup lang="ts">
2
- import type { SelectionItem } from './types'
3
2
  import { useWakeLock } from '@vueuse/core'
4
- import { slideScale, wakeLockEnabled } from '../state'
5
- import SelectList from './SelectList.vue'
6
-
7
- const scaleItems: SelectionItem<number>[] = [
8
- {
9
- display: 'Fit',
10
- value: 0,
11
- },
12
- {
13
- display: '1:1',
14
- value: 1,
15
- },
16
- ]
3
+ import { useNav } from '../composables/useNav'
4
+ import { hideCursorIdle, slideScale, viewerCssFilter, viewerCssFilterDefaults, wakeLockEnabled } from '../state'
5
+ import FormCheckbox from './FormCheckbox.vue'
6
+ import FormItem from './FormItem.vue'
7
+ import FormSlider from './FormSlider.vue'
8
+ import SegmentControl from './SegmentControl.vue'
17
9
 
10
+ const { isPresenter } = useNav()
18
11
  const { isSupported } = useWakeLock()
19
-
20
- const wakeLockItems: SelectionItem<boolean>[] = [
21
- {
22
- display: 'Enabled',
23
- value: true,
24
- },
25
- {
26
- display: 'Disabled',
27
- value: false,
28
- },
29
- ]
30
12
  </script>
31
13
 
32
14
  <template>
33
- <div class="text-sm select-none mb-2">
34
- <SelectList v-model="slideScale" title="Scale" :items="scaleItems" />
35
- <SelectList
15
+ <div text-sm select-none flex="~ col gap-1" min-w-30 px4>
16
+ <FormItem
17
+ title="Invert"
18
+ :dot="viewerCssFilter.invert !== viewerCssFilterDefaults.invert"
19
+ @reset="viewerCssFilter.invert = viewerCssFilterDefaults.invert"
20
+ >
21
+ <FormCheckbox v-model="viewerCssFilter.invert" />
22
+ </FormItem>
23
+ <FormItem
24
+ title="Brightness"
25
+ :dot="viewerCssFilter.brightness !== viewerCssFilterDefaults.brightness"
26
+ @reset="viewerCssFilter.brightness = viewerCssFilterDefaults.brightness"
27
+ >
28
+ <FormSlider
29
+ v-model="viewerCssFilter.brightness"
30
+ :max="1.5"
31
+ :min="0.5"
32
+ :step="0.02"
33
+ :default="viewerCssFilterDefaults.brightness"
34
+ />
35
+ </FormItem>
36
+ <FormItem
37
+ title="Contrast"
38
+ :dot="viewerCssFilter.contrast !== viewerCssFilterDefaults.contrast"
39
+ @reset="viewerCssFilter.contrast = viewerCssFilterDefaults.contrast"
40
+ >
41
+ <FormSlider
42
+ v-model="viewerCssFilter.contrast"
43
+ :max="1.5"
44
+ :min="0.5"
45
+ :step="0.02"
46
+ :default="viewerCssFilterDefaults.contrast"
47
+ />
48
+ </FormItem>
49
+ <FormItem
50
+ title="Saturation"
51
+ :dot="viewerCssFilter.saturate !== viewerCssFilterDefaults.saturate"
52
+ @reset="viewerCssFilter.saturate = viewerCssFilterDefaults.saturate"
53
+ >
54
+ <FormSlider
55
+ v-model="viewerCssFilter.saturate"
56
+ :max="1.5"
57
+ :min="0.5"
58
+ :step="0.02"
59
+ :default="viewerCssFilterDefaults.saturate"
60
+ />
61
+ </FormItem>
62
+ <FormItem
63
+ title="Sepia"
64
+ :dot="viewerCssFilter.sepia !== viewerCssFilterDefaults.sepia"
65
+ @reset="viewerCssFilter.sepia = viewerCssFilterDefaults.sepia"
66
+ >
67
+ <FormSlider
68
+ v-model="viewerCssFilter.sepia"
69
+ :max="2"
70
+ :min="-2"
71
+ :step="0.02"
72
+ :default="viewerCssFilterDefaults.sepia"
73
+ />
74
+ </FormItem>
75
+ <FormItem
76
+ title="Hue Rotate"
77
+ :dot="viewerCssFilter.hueRotate !== viewerCssFilterDefaults.hueRotate"
78
+ @reset="viewerCssFilter.hueRotate = viewerCssFilterDefaults.hueRotate"
79
+ >
80
+ <FormSlider
81
+ v-model="viewerCssFilter.hueRotate"
82
+ :max="180"
83
+ :min="-180"
84
+ :step="0.1"
85
+ :default="viewerCssFilterDefaults.hueRotate"
86
+ />
87
+ </FormItem>
88
+ <div class="h-1px opacity-5 bg-current w-full my2" />
89
+ <FormItem
90
+ v-if="!isPresenter"
91
+ title="Slide Scale"
92
+ >
93
+ <SegmentControl
94
+ v-model="slideScale"
95
+ :options="[
96
+ { label: 'Fit', value: 0 },
97
+ { label: '1:1', value: 1 },
98
+ ]"
99
+ />
100
+ </FormItem>
101
+ <FormItem
36
102
  v-if="__SLIDEV_FEATURE_WAKE_LOCK__ && isSupported"
37
- v-model="wakeLockEnabled" title="Wake lock" :items="wakeLockItems"
38
- />
103
+ title="Wake Lock"
104
+ >
105
+ <FormCheckbox v-model="wakeLockEnabled" />
106
+ </FormItem>
107
+ <FormItem
108
+ v-if="!isPresenter"
109
+ title="Hide Idle Cursor"
110
+ >
111
+ <FormCheckbox v-model="hideCursorIdle" />
112
+ </FormItem>
39
113
  </div>
40
114
  </template>
@@ -1,9 +1,10 @@
1
1
  <script setup lang="ts">
2
2
  import { provideLocal, useElementSize, useStyleTag } from '@vueuse/core'
3
- import { computed, onMounted, ref } from 'vue'
3
+ import { computed, ref } from 'vue'
4
4
  import { useNav } from '../composables/useNav'
5
5
  import { injectionSlideElement, injectionSlideScale } from '../constants'
6
6
  import { slideAspect, slideHeight, slideWidth } from '../env'
7
+ import { isDark } from '../logic/dark'
7
8
  import { snapshotManager } from '../logic/snapshot'
8
9
  import { slideScale } from '../state'
9
10
 
@@ -26,6 +27,10 @@ const props = defineProps({
26
27
  type: Boolean,
27
28
  default: false,
28
29
  },
30
+ contentStyle: {
31
+ type: Object,
32
+ default: () => ({}),
33
+ },
29
34
  })
30
35
 
31
36
  const { isPrintMode } = useNav()
@@ -44,6 +49,7 @@ const scale = computed(() => {
44
49
  })
45
50
 
46
51
  const contentStyle = computed(() => ({
52
+ ...props.contentStyle,
47
53
  'height': `${slideHeight.value}px`,
48
54
  'width': `${slideWidth.value}px`,
49
55
  'transform': `translate(-50%, -50%) scale(${scale.value})`,
@@ -65,32 +71,41 @@ provideLocal(injectionSlideScale, scale)
65
71
  provideLocal(injectionSlideElement, slideElement)
66
72
 
67
73
  const snapshot = computed(() => {
68
- if (!props.useSnapshot || props.no == null)
74
+ if (props.no == null || !props.useSnapshot)
69
75
  return undefined
70
- return snapshotManager.getSnapshot(props.no)
71
- })
72
-
73
- onMounted(() => {
74
- if (container.value && props.useSnapshot && props.no != null) {
75
- snapshotManager.captureSnapshot(props.no, container.value)
76
- }
76
+ return snapshotManager.getSnapshot(props.no, isDark.value)
77
77
  })
78
78
  </script>
79
79
 
80
80
  <template>
81
- <div v-if="!snapshot" :id="isMain ? 'slide-container' : undefined" ref="container" class="slidev-slide-container" :style="containerStyle">
82
- <div :id="isMain ? 'slide-content' : undefined" ref="slideElement" class="slidev-slide-content" :style="contentStyle">
81
+ <div
82
+ v-if="!snapshot"
83
+ :id="isMain ? 'slide-container' : undefined"
84
+ ref="container"
85
+ class="slidev-slide-container"
86
+ :style="containerStyle"
87
+ >
88
+ <div
89
+ :id="isMain ? 'slide-content' : undefined"
90
+ ref="slideElement"
91
+ class="slidev-slide-content"
92
+ :style="contentStyle"
93
+ >
83
94
  <slot />
84
95
  </div>
85
96
  <slot name="controls" />
86
97
  </div>
87
- <!-- Image preview -->
88
- <img
89
- v-else
90
- :src="snapshot"
91
- class="w-full object-cover"
92
- :style="containerStyle"
93
- >
98
+ <!-- Image Snapshot -->
99
+ <div v-else class="slidev-slide-container w-full h-full relative">
100
+ <img
101
+ :src="snapshot"
102
+ class="w-full h-full object-cover"
103
+ :style="containerStyle"
104
+ >
105
+ <div absolute bottom-1 right-1 p0.5 text-cyan:75 bg-cyan:10 rounded title="Snapshot">
106
+ <div class="i-carbon-camera" />
107
+ </div>
108
+ </div>
94
109
  </template>
95
110
 
96
111
  <style scoped lang="postcss">
@@ -7,8 +7,7 @@ import { createFixedClicks } from '../composables/useClicks'
7
7
  import { useNav } from '../composables/useNav'
8
8
  import { useViewTransition } from '../composables/useViewTransition'
9
9
  import { CLICKS_MAX } from '../constants'
10
- import { skipTransition } from '../logic/hmr'
11
- import { activeDragElement } from '../state'
10
+ import { activeDragElement, disableTransition, hmrSkipTransition } from '../state'
12
11
  import DragControl from './DragControl.vue'
13
12
  import SlideWrapper from './SlideWrapper.vue'
14
13
 
@@ -64,7 +63,7 @@ const loadedRoutes = computed(() => isPrintMode.value
64
63
  function onAfterLeave() {
65
64
  // After transition, we disable it so HMR won't trigger it again
66
65
  // We will turn it back on `nav.go` so the normal navigation would still work
67
- skipTransition.value = true
66
+ hmrSkipTransition.value = true
68
67
  // recompute poppers after transition
69
68
  recomputeAllPoppers()
70
69
  }
@@ -76,8 +75,8 @@ function onAfterLeave() {
76
75
 
77
76
  <!-- Slides -->
78
77
  <component
79
- :is="hasViewTransition && !isPrintMode ? 'div' : TransitionGroup"
80
- v-bind="skipTransition || isPrintMode ? {} : currentTransition"
78
+ :is="(hasViewTransition && !isPrintMode && !hmrSkipTransition && !disableTransition) ? 'div' : TransitionGroup"
79
+ v-bind="(hmrSkipTransition || disableTransition || isPrintMode) ? {} : currentTransition"
81
80
  id="slideshow"
82
81
  tag="div"
83
82
  :class="{
@@ -0,0 +1,103 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import { useNav } from '../composables/useNav'
4
+ import { syncDirections } from '../state'
5
+ import IconButton from './IconButton.vue'
6
+ import MenuButton from './MenuButton.vue'
7
+ import SelectList from './SelectList.vue'
8
+
9
+ const { isPresenter } = useNav()
10
+
11
+ const shouldReceive = computed({
12
+ get: () => isPresenter.value
13
+ ? syncDirections.value.presenterReceive
14
+ : syncDirections.value.viewerReceive,
15
+ set(v) {
16
+ if (isPresenter.value) {
17
+ syncDirections.value.presenterReceive = v
18
+ }
19
+ else {
20
+ syncDirections.value.viewerReceive = v
21
+ }
22
+ },
23
+ })
24
+
25
+ const shouldSend = computed({
26
+ get: () => isPresenter.value
27
+ ? syncDirections.value.presenterSend
28
+ : syncDirections.value.viewerSend,
29
+ set(v) {
30
+ if (isPresenter.value) {
31
+ syncDirections.value.presenterSend = v
32
+ }
33
+ else {
34
+ syncDirections.value.viewerSend = v
35
+ }
36
+ },
37
+ })
38
+
39
+ const state = computed({
40
+ get: () => {
41
+ if (shouldReceive.value && shouldSend.value) {
42
+ return 'bidirectional'
43
+ }
44
+ if (shouldReceive.value && !shouldSend.value) {
45
+ return 'receive-only'
46
+ }
47
+ if (!shouldReceive.value && shouldSend.value) {
48
+ return 'send-only'
49
+ }
50
+ return 'off'
51
+ },
52
+ set(v) {
53
+ switch (v) {
54
+ case 'bidirectional':
55
+ shouldReceive.value = true
56
+ shouldSend.value = true
57
+ break
58
+ case 'receive-only':
59
+ shouldReceive.value = true
60
+ shouldSend.value = false
61
+ break
62
+ case 'send-only':
63
+ shouldReceive.value = false
64
+ shouldSend.value = true
65
+ break
66
+ case 'off':
67
+ shouldReceive.value = false
68
+ shouldSend.value = false
69
+ break
70
+ }
71
+ },
72
+ })
73
+ </script>
74
+
75
+ <template>
76
+ <MenuButton>
77
+ <template #button>
78
+ <IconButton title="Change sync settings">
79
+ <div class="i-ph:arrow-up-bold mx--1.2 scale-x-80" :class="shouldSend ? 'text-green6 dark:text-green' : 'op30'" />
80
+ <div class="i-ph:arrow-down-bold mx--1.2 scale-x-80" :class="shouldReceive ? 'text-green6 dark:text-green' : 'op30'" />
81
+ </IconButton>
82
+ </template>
83
+ <template #menu>
84
+ <div text-sm flex="~ col gap-2">
85
+ <div px3 ws-nowrap>
86
+ <span op75>Slides navigation syncing for </span>
87
+ <span font-bold text-primary>{{ isPresenter ? 'presenter' : 'viewer' }}</span>
88
+ </div>
89
+ <div class="h-1px opacity-10 bg-current w-full" />
90
+ <SelectList
91
+ v-model="state"
92
+ title="Sync Mode"
93
+ :items="[
94
+ { value: 'bidirectional', display: 'Bidirectional Sync' },
95
+ { value: 'receive-only', display: 'Receive Only' },
96
+ { value: 'send-only', display: 'Send Only' },
97
+ { value: 'off', display: 'Disable' },
98
+ ]"
99
+ />
100
+ </div>
101
+ </template>
102
+ </MenuButton>
103
+ </template>
package/logic/color.ts ADDED
@@ -0,0 +1,64 @@
1
+ import { isDark } from './dark'
2
+
3
+ /**
4
+ * Predefined color map for matching the branding
5
+ *
6
+ * Accpet a 6-digit hex color string or a hue number
7
+ * Hue numbers are preferred because they will adapt better contrast in light/dark mode
8
+ *
9
+ * Hue numbers reference:
10
+ * - 0: red
11
+ * - 30: orange
12
+ * - 60: yellow
13
+ * - 120: green
14
+ * - 180: cyan
15
+ * - 240: blue
16
+ * - 270: purple
17
+ */
18
+ const predefinedColorMap = {
19
+ error: 0,
20
+ client: 60,
21
+ Light: 60,
22
+ Dark: 240,
23
+ } as Record<string, number>
24
+
25
+ export function getHashColorFromString(
26
+ name: string,
27
+ opacity: number | string = 1,
28
+ ) {
29
+ if (predefinedColorMap[name])
30
+ return getHsla(predefinedColorMap[name], opacity)
31
+
32
+ let hash = 0
33
+ for (let i = 0; i < name.length; i++)
34
+ hash = name.charCodeAt(i) + ((hash << 5) - hash)
35
+ const hue = hash % 360
36
+ return getHsla(hue, opacity)
37
+ }
38
+
39
+ export function getHsla(
40
+ hue: number,
41
+ opacity: number | string = 1,
42
+ ) {
43
+ const saturation = hue === -1
44
+ ? 0
45
+ : isDark.value ? 50 : 100
46
+ const lightness = isDark.value ? 60 : 20
47
+ return `hsla(${hue}, ${saturation}%, ${lightness}%, ${opacity})`
48
+ }
49
+
50
+ export function getPluginColor(name: string, opacity = 1): string {
51
+ if (predefinedColorMap[name]) {
52
+ const color = predefinedColorMap[name]
53
+ if (typeof color === 'number') {
54
+ return getHsla(color, opacity)
55
+ }
56
+ else {
57
+ if (opacity === 1)
58
+ return color
59
+ const opacityHex = Math.floor(opacity * 255).toString(16).padStart(2, '0')
60
+ return color + opacityHex
61
+ }
62
+ }
63
+ return getHashColorFromString(name, opacity)
64
+ }
package/logic/snapshot.ts CHANGED
@@ -1,11 +1,24 @@
1
+ import type { SlidevContextNavFull } from '../composables/useNav'
2
+ import type { ScreenshotSession } from './screenshot'
3
+ import { sleep } from '@antfu/utils'
4
+ import { slideHeight, slideWidth } from '../env'
5
+ import { captureDelay, disableTransition } from '../state'
1
6
  import { snapshotState } from '../state/snapshot'
7
+ import { isDark } from './dark'
8
+ import { startScreenshotSession } from './screenshot'
2
9
  import { getSlide } from './slides'
3
10
 
11
+ const chromeVersion = window.navigator.userAgent.match(/Chrome\/(\d+)/)?.[1]
12
+ export const isScreenshotSupported = chromeVersion ? Number(chromeVersion) >= 94 : false
13
+
14
+ const initialWait = 100
15
+
4
16
  export class SlideSnapshotManager {
5
- private _capturePromises = new Map<number, Promise<void>>()
17
+ private _screenshotSession: ScreenshotSession | null = null
6
18
 
7
- getSnapshot(slideNo: number) {
8
- const data = snapshotState.state[slideNo]
19
+ getSnapshot(slideNo: number, isDark: boolean) {
20
+ const id = slideNo + (isDark ? '-dark' : '-light')
21
+ const data = snapshotState.state[id]
9
22
  if (!data) {
10
23
  return
11
24
  }
@@ -18,67 +31,75 @@ export class SlideSnapshotManager {
18
31
  }
19
32
  }
20
33
 
21
- async captureSnapshot(slideNo: number, el: HTMLElement, delay = 1000) {
34
+ private async saveSnapshot(slideNo: number, dataUrl: string, isDark: boolean) {
22
35
  if (!__DEV__)
23
- return
24
- if (this.getSnapshot(slideNo)) {
25
- return
26
- }
27
- if (this._capturePromises.has(slideNo)) {
28
- await this._capturePromises.get(slideNo)
29
- }
30
- const promise = this._captureSnapshot(slideNo, el, delay)
31
- .finally(() => {
32
- this._capturePromises.delete(slideNo)
33
- })
34
- this._capturePromises.set(slideNo, promise)
35
- await promise
36
- }
37
-
38
- private async _captureSnapshot(slideNo: number, el: HTMLElement, delay: number) {
39
- if (!__DEV__)
40
- return
36
+ return false
41
37
  const slide = getSlide(slideNo)
42
38
  if (!slide)
43
- return
39
+ return false
44
40
 
41
+ const id = slideNo + (isDark ? '-dark' : '-light')
45
42
  const revision = slide.meta.slide.revision
43
+ snapshotState.patch(id, {
44
+ revision,
45
+ image: dataUrl,
46
+ })
47
+ }
46
48
 
47
- // Retry until the slide is loaded
48
- let retries = 100
49
- while (retries-- > 0) {
50
- if (!el.querySelector('.slidev-slide-loading'))
51
- break
52
- await new Promise(r => setTimeout(r, 100))
53
- }
49
+ async startCapturing(nav: SlidevContextNavFull) {
50
+ if (!__DEV__)
51
+ return false
54
52
 
55
- // Artificial delay for the content to be loaded
56
- await new Promise(r => setTimeout(r, delay))
53
+ // TODO: show a dialog to confirm
54
+
55
+ if (this._screenshotSession) {
56
+ this._screenshotSession.dispose()
57
+ this._screenshotSession = null
58
+ }
57
59
 
58
- // Capture the snapshot
59
- const toImage = await import('html-to-image')
60
60
  try {
61
- const dataUrl = await toImage.toPng(el, {
62
- width: el.offsetWidth,
63
- height: el.offsetHeight,
64
- skipFonts: true,
65
- cacheBust: true,
66
- pixelRatio: 1.5,
67
- })
68
- if (revision !== slide.meta.slide.revision) {
69
- // eslint-disable-next-line no-console
70
- console.info('[Slidev] Slide', slideNo, 'changed, discarding the snapshot')
71
- return
61
+ this._screenshotSession = await startScreenshotSession(
62
+ slideWidth.value,
63
+ slideHeight.value,
64
+ )
65
+
66
+ disableTransition.value = true
67
+ nav.go(1, 0, true)
68
+
69
+ await sleep(initialWait + captureDelay.value)
70
+ while (true) {
71
+ if (!this._screenshotSession) {
72
+ break
73
+ }
74
+ this.saveSnapshot(
75
+ nav.currentSlideNo.value,
76
+ this._screenshotSession.screenshot(document.getElementById('slide-content')!),
77
+ isDark.value,
78
+ )
79
+ if (nav.hasNext.value) {
80
+ await sleep(captureDelay.value)
81
+ nav.nextSlide(true)
82
+ await sleep(captureDelay.value)
83
+ }
84
+ else {
85
+ break
86
+ }
72
87
  }
73
- snapshotState.patch(slideNo, {
74
- revision,
75
- image: dataUrl,
76
- })
77
- // eslint-disable-next-line no-console
78
- console.info('[Slidev] Snapshot captured for slide', slideNo)
88
+
89
+ // TODO: show a message when done
90
+
91
+ return true
79
92
  }
80
93
  catch (e) {
81
- console.error('[Slidev] Failed to capture snapshot for slide', slideNo, e)
94
+ console.error(e)
95
+ return false
96
+ }
97
+ finally {
98
+ disableTransition.value = false
99
+ if (this._screenshotSession) {
100
+ this._screenshotSession.dispose()
101
+ this._screenshotSession = null
102
+ }
82
103
  }
83
104
  }
84
105
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@slidev/client",
3
3
  "type": "module",
4
- "version": "0.50.0",
4
+ "version": "0.51.0-beta.2",
5
5
  "description": "Presentation slides for developers",
6
6
  "author": "antfu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -32,21 +32,20 @@
32
32
  "@iconify-json/carbon": "^1.2.5",
33
33
  "@iconify-json/ph": "^1.2.2",
34
34
  "@iconify-json/svg-spinners": "^1.2.2",
35
- "@shikijs/monaco": "^1.24.2",
36
- "@shikijs/vitepress-twoslash": "^1.24.2",
35
+ "@shikijs/monaco": "^1.24.4",
36
+ "@shikijs/vitepress-twoslash": "^1.24.4",
37
37
  "@slidev/rough-notation": "^0.1.0",
38
38
  "@typescript/ata": "^0.9.7",
39
39
  "@unhead/vue": "^1.11.14",
40
- "@unocss/reset": "^0.65.1",
41
- "@vueuse/core": "^12.0.0",
42
- "@vueuse/math": "^12.0.0",
40
+ "@unocss/reset": "^0.65.3",
41
+ "@vueuse/core": "^12.2.0",
42
+ "@vueuse/math": "^12.2.0",
43
43
  "@vueuse/motion": "^2.2.6",
44
44
  "drauu": "^0.4.2",
45
45
  "file-saver": "^2.0.5",
46
46
  "floating-vue": "^5.2.2",
47
47
  "fuse.js": "^7.0.0",
48
- "html-to-image": "^1.11.11",
49
- "katex": "^0.16.17",
48
+ "katex": "^0.16.18",
50
49
  "lz-string": "^1.5.0",
51
50
  "mermaid": "^11.4.1",
52
51
  "monaco-editor": "0.51.0",
@@ -54,17 +53,17 @@
54
53
  "pptxgenjs": "^3.12.0",
55
54
  "prettier": "^3.4.2",
56
55
  "recordrtc": "^5.6.2",
57
- "shiki": "^1.24.2",
56
+ "shiki": "^1.24.4",
58
57
  "shiki-magic-move": "^0.5.2",
59
58
  "typescript": "5.6.3",
60
- "unocss": "^0.65.1",
59
+ "unocss": "^0.65.3",
61
60
  "vue": "^3.5.13",
62
61
  "vue-router": "^4.5.0",
63
62
  "yaml": "^2.6.1",
64
- "@slidev/parser": "0.50.0",
65
- "@slidev/types": "0.50.0"
63
+ "@slidev/parser": "0.51.0-beta.2",
64
+ "@slidev/types": "0.51.0-beta.2"
66
65
  },
67
66
  "devDependencies": {
68
- "vite": "^6.0.3"
67
+ "vite": "^6.0.6"
69
68
  }
70
69
  }