@slidev/client 0.48.0-beta.8 → 0.48.0

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.
Files changed (98) hide show
  1. package/App.vue +7 -0
  2. package/builtin/Arrow.vue +2 -4
  3. package/builtin/CodeBlockWrapper.vue +33 -28
  4. package/builtin/KaTexBlockWrapper.vue +1 -1
  5. package/builtin/Link.vue +3 -1
  6. package/builtin/Mermaid.vue +4 -3
  7. package/builtin/Monaco.vue +166 -93
  8. package/builtin/ShikiMagicMove.vue +103 -0
  9. package/builtin/SlidevVideo.vue +1 -1
  10. package/builtin/Toc.vue +1 -1
  11. package/builtin/TocList.vue +4 -3
  12. package/builtin/Tweet.vue +12 -9
  13. package/builtin/VClick.ts +2 -1
  14. package/composables/useClicks.ts +19 -32
  15. package/composables/useDarkMode.ts +9 -0
  16. package/composables/useDrawings.ts +181 -0
  17. package/composables/useNav.ts +346 -44
  18. package/{logic/note.ts → composables/useSlideInfo.ts} +13 -16
  19. package/composables/useSwipeControls.ts +43 -0
  20. package/composables/useTocTree.ts +81 -0
  21. package/composables/useViewTransition.ts +7 -4
  22. package/constants.ts +4 -3
  23. package/context.ts +13 -6
  24. package/env.ts +7 -16
  25. package/index.html +1 -0
  26. package/index.ts +12 -0
  27. package/internals/ClicksSlider.vue +93 -0
  28. package/internals/CodeRunner.vue +142 -0
  29. package/internals/Controls.vue +2 -2
  30. package/internals/DomElement.vue +18 -0
  31. package/internals/DrawingControls.vue +15 -17
  32. package/internals/DrawingLayer.vue +6 -5
  33. package/internals/DrawingPreview.vue +4 -2
  34. package/internals/Goto.vue +9 -6
  35. package/internals/IconButton.vue +7 -3
  36. package/internals/NavControls.vue +31 -12
  37. package/internals/NoteDisplay.vue +131 -8
  38. package/internals/NoteEditable.vue +129 -0
  39. package/internals/NoteStatic.vue +8 -6
  40. package/internals/PrintContainer.vue +11 -8
  41. package/internals/PrintSlide.vue +11 -12
  42. package/internals/PrintSlideClick.vue +14 -19
  43. package/internals/{SlidesOverview.vue → QuickOverview.vue} +35 -22
  44. package/internals/RecordingControls.vue +1 -1
  45. package/internals/RecordingDialog.vue +5 -6
  46. package/internals/{Editor.vue → SideEditor.vue} +26 -17
  47. package/internals/SlideContainer.vue +13 -9
  48. package/internals/SlideLoading.vue +19 -0
  49. package/internals/SlideWrapper.vue +79 -0
  50. package/internals/SlidesShow.vue +36 -22
  51. package/layouts/error.vue +5 -0
  52. package/layouts/two-cols-header.vue +9 -3
  53. package/logic/overview.ts +2 -2
  54. package/logic/route.ts +16 -5
  55. package/logic/slides.ts +20 -0
  56. package/logic/transition.ts +50 -0
  57. package/logic/utils.ts +24 -1
  58. package/main.ts +3 -15
  59. package/{setup → modules}/codemirror.ts +1 -3
  60. package/modules/context.ts +1 -46
  61. package/modules/mermaid.ts +9 -8
  62. package/package.json +21 -15
  63. package/{internals/EntrySelect.vue → pages/entry.vue} +7 -0
  64. package/{internals/NotesView.vue → pages/notes.vue} +9 -6
  65. package/pages/overview.vue +231 -0
  66. package/{internals/Play.vue → pages/play.vue} +22 -15
  67. package/{internals/PresenterPrint.vue → pages/presenter/print.vue} +15 -8
  68. package/{internals/Presenter.vue → pages/presenter.vue} +129 -107
  69. package/{internals/Print.vue → pages/print.vue} +6 -5
  70. package/routes.ts +26 -57
  71. package/setup/code-runners.ts +164 -0
  72. package/setup/main.ts +39 -9
  73. package/setup/mermaid.ts +5 -6
  74. package/setup/monaco.ts +114 -51
  75. package/setup/root.ts +62 -18
  76. package/setup/shortcuts.ts +15 -12
  77. package/shim-vue.d.ts +34 -0
  78. package/shim.d.ts +1 -13
  79. package/state/index.ts +2 -2
  80. package/styles/code.css +9 -5
  81. package/styles/index.css +63 -7
  82. package/styles/katex.css +1 -1
  83. package/styles/layouts-base.css +17 -12
  84. package/styles/shiki-twoslash.css +1 -1
  85. package/styles/vars.css +1 -0
  86. package/uno.config.ts +14 -2
  87. package/utils.ts +15 -2
  88. package/composables/useContext.ts +0 -17
  89. package/composables/useTweetScript.ts +0 -17
  90. package/iframes/monaco/index.css +0 -28
  91. package/iframes/monaco/index.html +0 -7
  92. package/iframes/monaco/index.ts +0 -260
  93. package/internals/NoteEditor.vue +0 -88
  94. package/internals/SlideWrapper.ts +0 -58
  95. package/logic/drawings.ts +0 -161
  96. package/logic/nav.ts +0 -278
  97. package/setup/prettier.ts +0 -43
  98. /package/{composables → logic}/hmr.ts +0 -0
@@ -1,29 +1,24 @@
1
1
  <script setup lang="ts">
2
- import type { RouteRecordRaw } from 'vue-router'
3
2
  import { computed, reactive, shallowRef } from 'vue'
4
- import type { ClicksContext } from '@slidev/types'
5
3
  import { provideLocal } from '@vueuse/core'
6
4
  import { injectionSlidevContext } from '../constants'
7
5
  import { configs, slideHeight, slideWidth } from '../env'
8
6
  import { getSlideClass } from '../utils'
9
- import type { SlidevContextNav } from '../modules/context'
10
- import SlideWrapper from './SlideWrapper'
7
+ import type { SlidevContextNav } from '../composables/useNav'
8
+ import SlideWrapper from './SlideWrapper.vue'
11
9
 
12
- // @ts-expect-error virtual module
13
- import GlobalTop from '/@slidev/global-components/top'
10
+ import GlobalTop from '#slidev/global-components/top'
11
+ import GlobalBottom from '#slidev/global-components/bottom'
14
12
 
15
- // @ts-expect-error virtual module
16
- import GlobalBottom from '/@slidev/global-components/bottom'
17
-
18
- const props = defineProps<{
19
- clicksContext: ClicksContext
13
+ const { nav } = defineProps<{
20
14
  nav: SlidevContextNav
21
- route: RouteRecordRaw
22
15
  }>()
23
16
 
17
+ const route = computed(() => nav.currentSlideRoute.value)
18
+
24
19
  const style = computed(() => ({
25
- height: `${slideHeight}px`,
26
- width: `${slideWidth}px`,
20
+ height: `${slideHeight.value}px`,
21
+ width: `${slideWidth.value}px`,
27
22
  }))
28
23
 
29
24
  const DrawingPreview = shallowRef<any>()
@@ -31,11 +26,11 @@ if (__SLIDEV_FEATURE_DRAWINGS__ || __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
31
26
  import('./DrawingPreview.vue').then(v => (DrawingPreview.value = v.default))
32
27
 
33
28
  const id = computed(() =>
34
- `${props.route.path.toString().padStart(3, '0')}-${(props.nav.clicks.value + 1).toString().padStart(2, '0')}`,
29
+ `${route.value.no.toString().padStart(3, '0')}-${(nav.clicks.value + 1).toString().padStart(2, '0')}`,
35
30
  )
36
31
 
37
32
  provideLocal(injectionSlidevContext, reactive({
38
- nav: props.nav,
33
+ nav,
39
34
  configs,
40
35
  themeConfigs: computed(() => configs.themeConfig),
41
36
  }))
@@ -46,8 +41,8 @@ provideLocal(injectionSlidevContext, reactive({
46
41
  <GlobalBottom />
47
42
 
48
43
  <SlideWrapper
49
- :is="route?.component!"
50
- :clicks-context="clicksContext"
44
+ :is="route.component!"
45
+ :clicks-context="nav.clicksContext.value"
51
46
  :class="getSlideClass(route)"
52
47
  :route="route"
53
48
  />
@@ -58,7 +53,7 @@ provideLocal(injectionSlidevContext, reactive({
58
53
  && DrawingPreview
59
54
  "
60
55
  >
61
- <DrawingPreview :page="+route.path" />
56
+ <DrawingPreview :page="route.no" />
62
57
  </template>
63
58
 
64
59
  <GlobalTop />
@@ -1,22 +1,23 @@
1
1
  <script setup lang="ts">
2
2
  import { useEventListener, useVModel } from '@vueuse/core'
3
3
  import { computed, ref, watchEffect } from 'vue'
4
- import { themeVars } from '../env'
5
4
  import { breakpoints, showOverview, windowSize } from '../state'
6
- import { currentPage, go as goSlide, rawRoutes } from '../logic/nav'
7
5
  import { currentOverviewPage, overviewRowCount } from '../logic/overview'
8
- import { useFixedClicks } from '../composables/useClicks'
6
+ import { createFixedClicks } from '../composables/useClicks'
9
7
  import { getSlideClass } from '../utils'
8
+ import { CLICKS_MAX } from '../constants'
9
+ import { useNav } from '../composables/useNav'
10
10
  import SlideContainer from './SlideContainer.vue'
11
- import SlideWrapper from './SlideWrapper'
11
+ import SlideWrapper from './SlideWrapper.vue'
12
12
  import DrawingPreview from './DrawingPreview.vue'
13
13
  import IconButton from './IconButton.vue'
14
14
 
15
15
  const props = defineProps<{ modelValue: boolean }>()
16
-
17
16
  const emit = defineEmits(['update:modelValue'])
18
17
  const value = useVModel(props, 'modelValue', emit)
19
18
 
19
+ const { currentSlideNo, go: goSlide, slides } = useNav()
20
+
20
21
  function close() {
21
22
  value.value = false
22
23
  }
@@ -78,17 +79,17 @@ useEventListener('keypress', (e) => {
78
79
  keyboardBuffer.value += String(num)
79
80
 
80
81
  // beyond the number of slides, reset
81
- if (+keyboardBuffer.value >= rawRoutes.length) {
82
+ if (+keyboardBuffer.value >= slides.value.length) {
82
83
  keyboardBuffer.value = ''
83
84
  return
84
85
  }
85
86
 
86
- const extactMatch = rawRoutes.findIndex(i => i.path === keyboardBuffer.value)
87
+ const extactMatch = slides.value.findIndex(i => `/${i.no}` === keyboardBuffer.value)
87
88
  if (extactMatch !== -1)
88
89
  currentOverviewPage.value = extactMatch + 1
89
90
 
90
91
  // When the input number is the largest at the number of digits, we go to that page directly.
91
- if (+keyboardBuffer.value * 10 > rawRoutes.length) {
92
+ if (+keyboardBuffer.value * 10 > slides.value.length) {
92
93
  go(+keyboardBuffer.value)
93
94
  keyboardBuffer.value = ''
94
95
  }
@@ -97,7 +98,7 @@ useEventListener('keypress', (e) => {
97
98
  watchEffect(() => {
98
99
  // Watch currentPage, make sure every time we open overview,
99
100
  // we focus on the right page.
100
- currentOverviewPage.value = currentPage.value
101
+ currentOverviewPage.value = currentSlideNo.value
101
102
  // Watch rowCount, make sure up and down shortcut work correctly.
102
103
  overviewRowCount.value = rowCount.value
103
104
  })
@@ -112,7 +113,7 @@ watchEffect(() => {
112
113
  >
113
114
  <div
114
115
  v-show="value"
115
- class="bg-main !bg-opacity-75 p-16 overflow-y-auto backdrop-blur-5px fixed left-0 right-0 top-0 h-[calc(var(--vh,1vh)*100)]"
116
+ class="bg-main !bg-opacity-75 p-16 py-20 overflow-y-auto backdrop-blur-5px fixed left-0 right-0 top-0 h-[calc(var(--vh,1vh)*100)]"
116
117
  @click="close()"
117
118
  >
118
119
  <div
@@ -120,18 +121,17 @@ watchEffect(() => {
120
121
  :style="`grid-template-columns: repeat(auto-fit,minmax(${cardWidth}px,1fr))`"
121
122
  >
122
123
  <div
123
- v-for="(route, idx) of rawRoutes"
124
- :key="route.path"
124
+ v-for="(route, idx) of slides"
125
+ :key="route.no"
125
126
  class="relative"
126
127
  >
127
128
  <div
128
- class="inline-block border rounded border-opacity-50 overflow-hidden bg-main hover:border-$slidev-theme-primary transition"
129
- :class="(focus(idx + 1) || currentOverviewPage === idx + 1) ? 'border-$slidev-theme-primary' : 'border-gray-400'"
130
- :style="themeVars"
131
- @click="go(+route.path)"
129
+ class="inline-block border rounded overflow-hidden bg-main hover:border-primary transition"
130
+ :class="(focus(idx + 1) || currentOverviewPage === idx + 1) ? 'border-primary' : 'border-main'"
131
+ @click="go(route.no)"
132
132
  >
133
133
  <SlideContainer
134
- :key="route.path"
134
+ :key="route.no"
135
135
  :width="cardWidth"
136
136
  :clicks-disabled="true"
137
137
  class="pointer-events-none"
@@ -139,12 +139,12 @@ watchEffect(() => {
139
139
  <SlideWrapper
140
140
  :is="route.component"
141
141
  v-if="route?.component"
142
- :clicks-context="useFixedClicks(route, 99999)[1]"
142
+ :clicks-context="createFixedClicks(route, CLICKS_MAX)"
143
143
  :class="getSlideClass(route)"
144
144
  :route="route"
145
145
  render-context="overview"
146
146
  />
147
- <DrawingPreview :page="+route.path" />
147
+ <DrawingPreview :page="route.no" />
148
148
  </SlideContainer>
149
149
  </div>
150
150
  <div
@@ -163,7 +163,20 @@ watchEffect(() => {
163
163
  </div>
164
164
  </div>
165
165
  </Transition>
166
- <IconButton v-if="value" title="Close" class="fixed text-2xl top-4 right-4 text-gray-400" @click="close">
167
- <carbon:close />
168
- </IconButton>
166
+ <div v-if="value" class="fixed top-4 right-4 text-gray-400 flex flex-col items-center gap-2">
167
+ <IconButton title="Close" class="text-2xl" @click="close">
168
+ <carbon:close />
169
+ </IconButton>
170
+ <IconButton
171
+ v-if="__DEV__"
172
+ as="a"
173
+ title="Slides Overview"
174
+ target="_blank"
175
+ href="/overview"
176
+ tab-index="-1"
177
+ class="text-2xl"
178
+ >
179
+ <carbon:list-boxes />
180
+ </IconButton>
181
+ </div>
169
182
  </template>
@@ -54,7 +54,7 @@ onMounted(() => {
54
54
  </IconButton>
55
55
  <MenuButton :disabled="recording">
56
56
  <template #button>
57
- <IconButton title="Select recording device" class="h-full !text-sm !px-0">
57
+ <IconButton title="Select recording device" class="h-full !text-sm !px-0 aspect-initial">
58
58
  <carbon:chevron-up class="opacity-50" />
59
59
  </IconButton>
60
60
  </template>
@@ -108,19 +108,18 @@ async function start() {
108
108
  }
109
109
  }
110
110
 
111
- input[type="text"] {
112
- @apply border border-gray-400 rounded px-2 py-1;
111
+ input[type='text'] {
112
+ @apply border border-main rounded px-2 py-1;
113
113
  }
114
114
 
115
115
  button {
116
116
  @apply bg-orange-400 text-white px-4 py-1 rounded border-b-2 border-orange-600;
117
- @apply hover:(bg-orange-500 border-orange-700)
117
+ @apply hover:(bg-orange-500 border-orange-700);
118
118
  }
119
119
 
120
120
  button.cancel {
121
- @apply bg-gray-400 text-white px-4 py-1 rounded border-b-2 border-gray-500;
122
- @apply bg-opacity-50 border-opacity-50;
123
- @apply hover:(bg-opacity-75 border-opacity-75)
121
+ @apply bg-gray-400 bg-opacity-50 text-white px-4 py-1 rounded border-b-2 border-main;
122
+ @apply hover:(bg-opacity-75 border-opacity-75);
124
123
  }
125
124
  }
126
125
  </style>
@@ -1,16 +1,18 @@
1
1
  <script setup lang="ts">
2
- import { throttledWatch, useEventListener } from '@vueuse/core'
2
+ import { throttledWatch, useEventListener, watchThrottled } from '@vueuse/core'
3
3
  import { computed, nextTick, onMounted, ref, watch } from 'vue'
4
- import { activeElement, editorHeight, editorWidth, isInputting, showEditor, isEditorVertical as vertical } from '../state'
5
- import { useCodeMirror } from '../setup/codemirror'
6
- import { currentSlideId, openInEditor } from '../logic/nav'
7
- import { useDynamicSlideInfo } from '../logic/note'
4
+ import { activeElement, editorHeight, editorWidth, isEditorVertical, isInputting, showEditor, isEditorVertical as vertical } from '../state'
5
+ import { useCodeMirror } from '../modules/codemirror'
6
+ import { useNav } from '../composables/useNav'
7
+ import { useDynamicSlideInfo } from '../composables/useSlideInfo'
8
8
  import IconButton from './IconButton.vue'
9
9
 
10
10
  const props = defineProps<{
11
11
  resize?: boolean
12
12
  }>()
13
13
 
14
+ const { currentSlideNo, openInEditor } = useNav()
15
+
14
16
  const tab = ref<'content' | 'note'>('content')
15
17
  const content = ref('')
16
18
  const note = ref('')
@@ -19,7 +21,7 @@ const frontmatter = ref<any>({})
19
21
  const contentInput = ref<HTMLTextAreaElement>()
20
22
  const noteInput = ref<HTMLTextAreaElement>()
21
23
 
22
- const { info, update } = useDynamicSlideInfo(currentSlideId)
24
+ const { info, update } = useDynamicSlideInfo(currentSlideNo)
23
25
 
24
26
  watch(
25
27
  info,
@@ -94,16 +96,23 @@ onMounted(async () => {
94
96
  },
95
97
  )
96
98
 
97
- watch([tab, vertical], () => {
98
- nextTick(() => {
99
- if (tab.value === 'content')
100
- contentEditor.refresh()
101
- else
102
- noteEditor.refresh()
103
- })
104
- })
99
+ watchThrottled(
100
+ [tab, vertical, isEditorVertical, editorWidth, editorHeight],
101
+ () => {
102
+ nextTick(() => {
103
+ if (tab.value === 'content')
104
+ contentEditor.refresh()
105
+ else
106
+ noteEditor.refresh()
107
+ })
108
+ },
109
+ {
110
+ throttle: 100,
111
+ flush: 'post',
112
+ },
113
+ )
105
114
 
106
- watch(currentSlideId, () => {
115
+ watch(currentSlideNo, () => {
107
116
  contentEditor.clearHistory()
108
117
  noteEditor.clearHistory()
109
118
  }, { flush: 'post' })
@@ -172,13 +181,13 @@ throttledWatch(
172
181
  <div class="flex pb-2 text-xl -mt-1">
173
182
  <div class="mr-4 rounded flex">
174
183
  <IconButton
175
- title="Switch to content tab" :class="tab === 'content' ? 'text-$slidev-theme-primary' : ''"
184
+ title="Switch to content tab" :class="tab === 'content' ? 'text-primary' : ''"
176
185
  @click="switchTab('content')"
177
186
  >
178
187
  <carbon:account />
179
188
  </IconButton>
180
189
  <IconButton
181
- title="Switch to notes tab" :class="tab === 'note' ? 'text-$slidev-theme-primary' : ''"
190
+ title="Switch to notes tab" :class="tab === 'note' ? 'text-primary' : ''"
182
191
  @click="switchTab('note')"
183
192
  >
184
193
  <carbon:align-box-bottom-right />
@@ -3,7 +3,7 @@ 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
5
  import { injectionSlideScale } from '../constants'
6
- import { isPrintMode } from '../logic/nav'
6
+ import { useNav } from '../composables/useNav'
7
7
 
8
8
  const props = defineProps({
9
9
  width: {
@@ -21,11 +21,13 @@ const props = defineProps({
21
21
  },
22
22
  })
23
23
 
24
+ const { clicksDirection, isPrintMode } = useNav()
25
+
24
26
  const root = ref<HTMLDivElement>()
25
27
  const element = useElementSize(root)
26
28
 
27
29
  const width = computed(() => props.width ? props.width : element.width.value)
28
- const height = computed(() => props.width ? props.width / slideAspect : element.height.value)
30
+ const height = computed(() => props.width ? props.width / slideAspect.value : element.height.value)
29
31
 
30
32
  if (props.width) {
31
33
  watchEffect(() => {
@@ -41,20 +43,22 @@ const screenAspect = computed(() => width.value / height.value)
41
43
  const scale = computed(() => {
42
44
  if (props.scale && !isPrintMode.value)
43
45
  return props.scale
44
- if (screenAspect.value < slideAspect)
45
- return width.value / slideWidth
46
- return height.value * slideAspect / slideWidth
46
+ if (screenAspect.value < slideAspect.value)
47
+ return width.value / slideWidth.value
48
+ return height.value * slideAspect.value / slideWidth.value
47
49
  })
48
50
 
49
51
  const style = computed(() => ({
50
- 'height': `${slideHeight}px`,
51
- 'width': `${slideWidth}px`,
52
+ 'height': `${slideHeight.value}px`,
53
+ 'width': `${slideWidth.value}px`,
52
54
  'transform': `translate(-50%, -50%) scale(${scale.value})`,
53
55
  '--slidev-slide-scale': scale.value,
54
56
  }))
55
57
 
56
58
  const className = computed(() => ({
57
59
  'select-none': !configs.selectable,
60
+ 'slidev-nav-go-forward': clicksDirection.value > 0,
61
+ 'slidev-nav-go-backward': clicksDirection.value < 0,
58
62
  }))
59
63
 
60
64
  if (props.isMain) {
@@ -69,8 +73,8 @@ provideLocal(injectionSlideScale, scale as any)
69
73
  </script>
70
74
 
71
75
  <template>
72
- <div id="slide-container" ref="root" :class="className">
73
- <div id="slide-content" :style="style">
76
+ <div id="slide-container" ref="root" class="slidev-slides-container" :class="className">
77
+ <div id="slide-content" class="slidev-slide-content" :style="style">
74
78
  <slot />
75
79
  </div>
76
80
  <slot name="controls" />
@@ -0,0 +1,19 @@
1
+ <script setup lang="ts">
2
+ import { onMounted, ref } from 'vue'
3
+
4
+ const timeout = ref(false)
5
+ onMounted(() => {
6
+ setTimeout(() => {
7
+ timeout.value = true
8
+ }, 200)
9
+ })
10
+ </script>
11
+
12
+ <template>
13
+ <div class="h-full w-full flex items-center justify-center gap-2 slidev-slide-loading">
14
+ <template v-if="timeout">
15
+ <div class="i-svg-spinners-90-ring-with-bg text-xl" />
16
+ <div>Loading slide...</div>
17
+ </template>
18
+ </div>
19
+ </template>
@@ -0,0 +1,79 @@
1
+ <script setup lang="ts">
2
+ import { computed, defineAsyncComponent, defineComponent, h, onMounted, ref, toRef } from 'vue'
3
+ import type { PropType } from 'vue'
4
+ import { provideLocal } from '@vueuse/core'
5
+ import type { ClicksContext, RenderContext, SlideRoute } from '@slidev/types'
6
+ import { injectionActive, injectionClicksContext, injectionCurrentPage, injectionRenderContext, injectionRoute } from '../constants'
7
+ import SlideLoading from './SlideLoading.vue'
8
+
9
+ const props = defineProps({
10
+ clicksContext: {
11
+ type: Object as PropType<ClicksContext>,
12
+ required: true,
13
+ },
14
+ renderContext: {
15
+ type: String as PropType<RenderContext>,
16
+ default: 'slide',
17
+ },
18
+ active: {
19
+ type: Boolean,
20
+ default: false,
21
+ },
22
+ is: {
23
+ type: Function as PropType<() => any>,
24
+ required: true,
25
+ },
26
+ route: {
27
+ type: Object as PropType<SlideRoute>,
28
+ required: true,
29
+ },
30
+ })
31
+
32
+ provideLocal(injectionRoute, props.route)
33
+ provideLocal(injectionCurrentPage, ref(props.route.no))
34
+ provideLocal(injectionRenderContext, ref(props.renderContext as RenderContext))
35
+ provideLocal(injectionActive, toRef(props, 'active'))
36
+ provideLocal(injectionClicksContext, toRef(props, 'clicksContext'))
37
+
38
+ const style = computed(() => {
39
+ const zoom = props.route.meta?.slide?.frontmatter.zoom ?? 1
40
+ return zoom === 1
41
+ ? undefined
42
+ : {
43
+ width: `${100 / zoom}%`,
44
+ height: `${100 / zoom}%`,
45
+ transformOrigin: 'top left',
46
+ transform: `scale(${zoom})`,
47
+ }
48
+ })
49
+
50
+ const SlideComponent = defineAsyncComponent({
51
+ loader: async () => {
52
+ const component = await props.is()
53
+ return defineComponent({
54
+ setup(_, { attrs }) {
55
+ onMounted(() => {
56
+ props.clicksContext.onMounted()
57
+ })
58
+ return () => h(component.default, attrs)
59
+ },
60
+ })
61
+ },
62
+ delay: 300,
63
+ loadingComponent: SlideLoading,
64
+ })
65
+ </script>
66
+
67
+ <template>
68
+ <component
69
+ :is="SlideComponent"
70
+ :style="style"
71
+ :class="{ 'disable-view-transition': !['slide', 'presenter'].includes(props.renderContext) }"
72
+ />
73
+ </template>
74
+
75
+ <style scoped>
76
+ .disable-view-transition:deep(*) {
77
+ view-transition-name: none !important;
78
+ }
79
+ </style>
@@ -1,27 +1,33 @@
1
1
  <script setup lang="ts">
2
2
  import { TransitionGroup, computed, shallowRef, watch } from 'vue'
3
- import { currentRoute, isPresenter, nextRoute, rawRoutes, transition } from '../logic/nav'
3
+ import { recomputeAllPoppers } from 'floating-vue'
4
+ import { useNav } from '../composables/useNav'
4
5
  import { getSlideClass } from '../utils'
5
6
  import { useViewTransition } from '../composables/useViewTransition'
6
- import { skipTransition } from '../composables/hmr'
7
- import { usePrimaryClicks } from '../composables/useClicks'
8
- import SlideWrapper from './SlideWrapper'
9
-
10
- // @ts-expect-error virtual module
11
- import GlobalTop from '/@slidev/global-components/top'
12
-
13
- // @ts-expect-error virtual module
14
- import GlobalBottom from '/@slidev/global-components/bottom'
7
+ import { skipTransition } from '../logic/hmr'
8
+ import SlideWrapper from './SlideWrapper.vue'
15
9
  import PresenterMouse from './PresenterMouse.vue'
16
10
 
11
+ import GlobalTop from '#slidev/global-components/top'
12
+ import GlobalBottom from '#slidev/global-components/bottom'
13
+
17
14
  defineProps<{
18
15
  renderContext: 'slide' | 'presenter'
19
16
  }>()
20
17
 
18
+ const {
19
+ currentSlideRoute,
20
+ currentTransition,
21
+ getPrimaryClicks,
22
+ isPresenter,
23
+ nextRoute,
24
+ slides,
25
+ } = useNav()
26
+
21
27
  // preload next route
22
- watch(currentRoute, () => {
23
- if (currentRoute.value?.meta && currentRoute.value.meta.preload !== false)
24
- currentRoute.value.meta.__preloaded = true
28
+ watch(currentSlideRoute, () => {
29
+ if (currentSlideRoute.value?.meta && currentSlideRoute.value.meta.preload !== false)
30
+ currentSlideRoute.value.meta.__preloaded = true
25
31
  if (nextRoute.value?.meta && nextRoute.value.meta.preload !== false)
26
32
  nextRoute.value.meta.__preloaded = true
27
33
  }, { immediate: true })
@@ -32,12 +38,14 @@ const DrawingLayer = shallowRef<any>()
32
38
  if (__SLIDEV_FEATURE_DRAWINGS__ || __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
33
39
  import('./DrawingLayer.vue').then(v => DrawingLayer.value = v.default)
34
40
 
35
- const loadedRoutes = computed(() => rawRoutes.filter(r => r.meta?.__preloaded || r === currentRoute.value))
41
+ const loadedRoutes = computed(() => slides.value.filter(r => r.meta?.__preloaded || r === currentSlideRoute.value))
36
42
 
37
43
  function onAfterLeave() {
38
44
  // After transition, we disable it so HMR won't trigger it again
39
45
  // We will turn it back on `nav.go` so the normal navigation would still work
40
46
  skipTransition.value = true
47
+ // recompute poppers after transition
48
+ recomputeAllPoppers()
41
49
  }
42
50
  </script>
43
51
 
@@ -48,22 +56,25 @@ function onAfterLeave() {
48
56
  <!-- Slides -->
49
57
  <component
50
58
  :is="hasViewTransition ? 'div' : TransitionGroup"
51
- v-bind="skipTransition ? {} : transition"
59
+ v-bind="skipTransition ? {} : currentTransition"
52
60
  id="slideshow"
53
61
  tag="div"
54
62
  @after-leave="onAfterLeave"
55
63
  >
56
- <template v-for="route of loadedRoutes" :key="route.path">
64
+ <div
65
+ v-for="route of loadedRoutes"
66
+ v-show="route === currentSlideRoute"
67
+ :key="route.no"
68
+ >
57
69
  <SlideWrapper
58
- :is="route?.component as any"
59
- v-show="route === currentRoute"
60
- :clicks-context="usePrimaryClicks(route)"
70
+ :is="route.component!"
71
+ :clicks-context="getPrimaryClicks(route)"
61
72
  :class="getSlideClass(route)"
62
73
  :route="route"
63
74
  :render-context="renderContext"
64
75
  class="overflow-hidden"
65
76
  />
66
- </template>
77
+ </div>
67
78
  </component>
68
79
 
69
80
  <!-- Global Top -->
@@ -77,10 +88,13 @@ function onAfterLeave() {
77
88
 
78
89
  <style scoped>
79
90
  #slideshow {
80
- @apply h-full;
91
+ height: 100%;
81
92
  }
82
93
 
83
94
  #slideshow > div {
84
- @apply h-full w-full absolute;
95
+ position: absolute;
96
+ height: 100%;
97
+ width: 100%;
85
98
  }
86
99
  </style>
100
+ ../logic/hmr
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <div class="px-4 py-10 text-center text-red-700 dark:text-red-500 font-bold font-mono">
3
+ An error occurred on this slide. Check the terminal for more information.
4
+ </div>
5
+ </template>
@@ -51,9 +51,15 @@ const props = defineProps({
51
51
  grid-template-rows: repeat(2, 1fr);
52
52
  }
53
53
 
54
- .col-header { grid-area: 1 / 1 / 2 / 3; }
55
- .col-left { grid-area: 2 / 1 / 3 / 2; }
56
- .col-right { grid-area: 2 / 2 / 3 / 3; }
54
+ .col-header {
55
+ grid-area: 1 / 1 / 2 / 3;
56
+ }
57
+ .col-left {
58
+ grid-area: 2 / 1 / 3 / 2;
59
+ }
60
+ .col-right {
61
+ grid-area: 2 / 2 / 3 / 3;
62
+ }
57
63
  .col-bottom {
58
64
  align-self: end;
59
65
  grid-area: 3 / 1 / 3 / 3;
package/logic/overview.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { computed, ref } from 'vue'
2
- import { rawRoutes } from '../routes'
2
+ import { slides } from './slides'
3
3
 
4
4
  // To have same format(.value) as max, wrap it with ref.
5
5
  const min = ref(1)
6
- const max = computed(() => rawRoutes.length)
6
+ const max = computed(() => slides.value.length)
7
7
 
8
8
  export const currentOverviewPage = ref(0)
9
9
  export const overviewRowCount = ref(0)
package/logic/route.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { computed, nextTick, unref } from 'vue'
2
- import { router } from '../routes'
1
+ import type { WritableComputedRef } from 'vue'
2
+ import { computed, nextTick, ref, unref } from 'vue'
3
+ import { useRouter } from 'vue-router'
3
4
 
4
5
  export function useRouteQuery<T extends string | string[]>(
5
6
  name: string,
@@ -7,7 +8,9 @@ export function useRouteQuery<T extends string | string[]>(
7
8
  {
8
9
  mode = 'replace',
9
10
  } = {},
10
- ) {
11
+ ): WritableComputedRef<T> {
12
+ const router = useRouter()
13
+
11
14
  return computed<any>({
12
15
  get() {
13
16
  const data = router.currentRoute.value.query[name]
@@ -19,8 +22,16 @@ export function useRouteQuery<T extends string | string[]>(
19
22
  },
20
23
  set(v) {
21
24
  nextTick(() => {
22
- router[unref(mode) as 'replace' | 'push']({ query: { ...router.currentRoute.value.query, [name]: v } })
25
+ router[unref(mode) as 'replace' | 'push']({
26
+ query: {
27
+ ...router.currentRoute.value.query,
28
+ [name]: `${v}` === defaultValue ? undefined : v,
29
+ },
30
+ })
23
31
  })
24
32
  },
25
- })
33
+ }) as any
26
34
  }
35
+
36
+ // force update collected elements when the route is fully resolved
37
+ export const routeForceRefresh = ref(0)