@slidev/client 0.50.0-beta.10 → 0.50.0-beta.12

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,7 +1,7 @@
1
1
  import type { ClicksContext, NormalizedRangeClickValue, NormalizedSingleClickValue, RawAtValue, RawSingleAtValue, SlideRoute } from '@slidev/types'
2
- import type { Ref } from 'vue'
2
+ import type { MaybeRefOrGetter, Ref } from 'vue'
3
3
  import { clamp, sum } from '@antfu/utils'
4
- import { computed, onMounted, onUnmounted, ref, shallowReactive } from 'vue'
4
+ import { computed, isReadonly, onMounted, onUnmounted, ref, shallowReactive, toValue } from 'vue'
5
5
 
6
6
  export function normalizeSingleAtValue(at: RawSingleAtValue): NormalizedSingleClickValue {
7
7
  if (at === false || at === 'false')
@@ -59,7 +59,8 @@ export function createClicksContextBase(
59
59
  // Convert maxMap to reactive
60
60
  maxMap = shallowReactive(maxMap)
61
61
  // Make sure the query is not greater than the total
62
- context.current = current.value
62
+ if (!isReadonly(current))
63
+ context.current = current.value
63
64
  })
64
65
  onUnmounted(() => {
65
66
  isMounted.value = false
@@ -160,11 +161,11 @@ export function createClicksContextBase(
160
161
 
161
162
  export function createFixedClicks(
162
163
  route?: SlideRoute | undefined,
163
- currentInit = 0,
164
+ currentInit: MaybeRefOrGetter<number> = 0,
164
165
  ): ClicksContext {
165
166
  const clicksStart = route?.meta.slide?.frontmatter.clicksStart ?? 0
166
167
  return createClicksContextBase(
167
- ref(Math.max(currentInit, clicksStart)),
168
+ computed(() => Math.max(toValue(currentInit), clicksStart)),
168
169
  clicksStart,
169
170
  route?.meta?.clicks,
170
171
  )
@@ -7,6 +7,7 @@ import { injectionCurrentPage, injectionFrontmatter, injectionRenderContext, inj
7
7
  import { makeId } from '../logic/utils'
8
8
  import { activeDragElement } from '../state'
9
9
  import { directiveInject } from '../utils'
10
+ import { useNav } from './useNav'
10
11
  import { useSlideBounds } from './useSlideBounds'
11
12
  import { useDynamicSlideInfo } from './useSlideInfo'
12
13
 
@@ -70,24 +71,24 @@ export function useDragElementsUpdater(no: number) {
70
71
  section = type === 'prop'
71
72
  // eslint-disable-next-line regexp/no-super-linear-backtracking
72
73
  ? section.replace(/<(v-?drag-?\w*)(.*?)(\/)?>/gi, (full, tag, attrs, selfClose = '', index) => {
73
- if (index === idx) {
74
- replaced = true
75
- const posMatch = attrs.match(/pos=".*?"/)
76
- if (!posMatch)
77
- return `<${tag}${ensureSuffix(' ', attrs)}pos="${posStr}"${selfClose}>`
78
- const start = posMatch.index
79
- const end = start + posMatch[0].length
80
- return `<${tag}${attrs.slice(0, start)}pos="${posStr}"${attrs.slice(end)}${selfClose}>`
81
- }
82
- return full
83
- })
74
+ if (index === idx) {
75
+ replaced = true
76
+ const posMatch = attrs.match(/pos=".*?"/)
77
+ if (!posMatch)
78
+ return `<${tag}${ensureSuffix(' ', attrs)}pos="${posStr}"${selfClose}>`
79
+ const start = posMatch.index
80
+ const end = start + posMatch[0].length
81
+ return `<${tag}${attrs.slice(0, start)}pos="${posStr}"${attrs.slice(end)}${selfClose}>`
82
+ }
83
+ return full
84
+ })
84
85
  : section.replace(/(?<![</\w])v-drag(?:=".*?")?/gi, (full, index) => {
85
- if (index === idx) {
86
- replaced = true
87
- return `v-drag="${posStr}"`
88
- }
89
- return full
90
- })
86
+ if (index === idx) {
87
+ replaced = true
88
+ return `v-drag="${posStr}"`
89
+ }
90
+ return full
91
+ })
91
92
 
92
93
  if (!replaced)
93
94
  throw new Error(`[Slidev] VDrag Element ${id} is not found in the markdown source`)
@@ -127,7 +128,8 @@ export function useDragElement(directive: DirectiveBinding | null, posRaw?: stri
127
128
  const scale = inject(injectionSlideScale) ?? ref(1)
128
129
  const zoom = inject(injectionSlideZoom) ?? ref(1)
129
130
  const { left: slideLeft, top: slideTop, stop: stopWatchBounds } = useSlideBounds(inject(injectionSlideElement) ?? ref())
130
- const enabled = ['slide', 'presenter'].includes(renderContext.value)
131
+ const { isPrintMode } = useNav()
132
+ const enabled = ['slide', 'presenter'].includes(renderContext.value) && !isPrintMode.value
131
133
 
132
134
  let dataSource: DragElementDataSource = directive ? 'directive' : 'prop'
133
135
  let dragId: string = makeId()
@@ -180,16 +182,16 @@ export function useDragElement(directive: DirectiveBinding | null, posRaw?: stri
180
182
  const configuredHeight = ref(pos[3] ?? 0)
181
183
  const height = autoHeight
182
184
  ? computed({
183
- get: () => (autoHeight ? actualHeight.value : configuredHeight.value) || 0,
184
- set: v => !autoHeight && (configuredHeight.value = v),
185
- })
185
+ get: () => (autoHeight ? actualHeight.value : configuredHeight.value) || 0,
186
+ set: v => !autoHeight && (configuredHeight.value = v),
187
+ })
186
188
  : configuredHeight
187
189
  const configuredY0 = autoHeight ? ref(pos[1]) : ref(pos[1] + pos[3] / 2)
188
190
  const y0 = autoHeight
189
191
  ? computed({
190
- get: () => configuredY0.value + height.value / 2,
191
- set: v => configuredY0.value = v - height.value / 2,
192
- })
192
+ get: () => configuredY0.value + height.value / 2,
193
+ set: v => configuredY0.value = v - height.value / 2,
194
+ })
193
195
  : configuredY0
194
196
 
195
197
  const containerStyle = computed(() => {
@@ -266,10 +268,14 @@ export function useDragElement(directive: DirectiveBinding | null, posRaw?: stri
266
268
  state.stopDragging()
267
269
  },
268
270
  startDragging(): void {
271
+ if (!enabled)
272
+ return
269
273
  updateBounds()
270
274
  activeDragElement.value = state
271
275
  },
272
276
  stopDragging(): void {
277
+ if (!enabled)
278
+ return
273
279
  if (activeDragElement.value === state)
274
280
  activeDragElement.value = null
275
281
  },
@@ -3,10 +3,10 @@ import type { ComputedRef, Ref, TransitionGroupProps, WritableComputedRef } from
3
3
  import type { RouteLocationNormalized, Router } from 'vue-router'
4
4
  import { slides } from '#slidev/slides'
5
5
  import { clamp } from '@antfu/utils'
6
+ import { parseRangeString } from '@slidev/parser/utils'
6
7
  import { createSharedComposable } from '@vueuse/core'
7
- import { logicOr } from '@vueuse/math'
8
8
  import { computed, ref, watch } from 'vue'
9
- import { useRouter } from 'vue-router'
9
+ import { useRoute, useRouter } from 'vue-router'
10
10
  import { CLICKS_MAX } from '../constants'
11
11
  import { configs } from '../env'
12
12
  import { skipTransition } from '../logic/hmr'
@@ -71,7 +71,7 @@ export interface SlidevContextNavState {
71
71
  router: Router
72
72
  currentRoute: ComputedRef<RouteLocationNormalized>
73
73
  isPrintMode: ComputedRef<boolean>
74
- isPrintWithClicks: ComputedRef<boolean>
74
+ isPrintWithClicks: Ref<boolean>
75
75
  isEmbedded: ComputedRef<boolean>
76
76
  isPlaying: ComputedRef<boolean>
77
77
  isPresenter: ComputedRef<boolean>
@@ -83,6 +83,7 @@ export interface SlidevContextNavState {
83
83
  clicksContext: ComputedRef<ClicksContext>
84
84
  queryClicksRaw: Ref<string>
85
85
  queryClicks: WritableComputedRef<number>
86
+ printRange: Ref<number[]>
86
87
  getPrimaryClicks: (route: SlideRoute) => ClicksContext
87
88
  }
88
89
 
@@ -113,7 +114,7 @@ export function useNavBase(
113
114
  const hasNext = computed(() => currentSlideNo.value < slides.value.length || clicks.value < clicksTotal.value)
114
115
  const hasPrev = computed(() => currentSlideNo.value > 1 || clicks.value > 0)
115
116
 
116
- const currentTransition = computed(() => getCurrentTransition(navDirection.value, currentSlideRoute.value, prevRoute.value))
117
+ const currentTransition = computed(() => isPrint.value ? undefined : getCurrentTransition(navDirection.value, currentSlideRoute.value, prevRoute.value))
117
118
 
118
119
  watch(currentSlideRoute, (next, prev) => {
119
120
  navDirection.value = next.no - prev.no
@@ -191,7 +192,7 @@ export function useNavBase(
191
192
  clicks = clamp(clicks, clicksStart, meta?.__clicksContext?.total ?? CLICKS_MAX)
192
193
  if (force || pageChanged || clicksChanged) {
193
194
  await router?.push({
194
- path: getSlidePath(no, isPresenter.value),
195
+ path: getSlidePath(no, isPresenter.value, router.currentRoute.value.name === 'export'),
195
196
  query: {
196
197
  ...router.currentRoute.value.query,
197
198
  clicks: clicks === 0 ? undefined : clicks.toString(),
@@ -272,24 +273,24 @@ export function useFixedNav(
272
273
 
273
274
  const useNavState = createSharedComposable((): SlidevContextNavState => {
274
275
  const router = useRouter()
276
+ const currentRoute = useRoute()
275
277
 
276
- const currentRoute = computed(() => router.currentRoute.value)
277
278
  const query = computed(() => {
278
279
  // eslint-disable-next-line ts/no-unused-expressions
279
280
  router.currentRoute.value.query
280
281
  return new URLSearchParams(location.search)
281
282
  })
282
- const isPrintMode = computed(() => query.value.has('print'))
283
- const isPrintWithClicks = computed(() => query.value.get('print') === 'clicks')
283
+ const isPrintMode = computed(() => query.value.has('print') || currentRoute.name === 'export')
284
+ const isPrintWithClicks = ref(query.value.get('print') === 'clicks')
284
285
  const isEmbedded = computed(() => query.value.has('embedded'))
285
- const isPlaying = computed(() => currentRoute.value.name === 'play')
286
- const isPresenter = computed(() => currentRoute.value.name === 'presenter')
287
- const isNotesViewer = computed(() => currentRoute.value.name === 'notes')
286
+ const isPlaying = computed(() => currentRoute.name === 'play')
287
+ const isPresenter = computed(() => currentRoute.name === 'presenter')
288
+ const isNotesViewer = computed(() => currentRoute.name === 'notes')
288
289
  const isPresenterAvailable = computed(() => !isPresenter.value && (!configs.remote || query.value.get('password') === configs.remote))
289
- const hasPrimarySlide = logicOr(isPlaying, isPresenter)
290
-
291
- const currentSlideNo = computed(() => hasPrimarySlide.value ? getSlide(currentRoute.value.params.no as string)?.no ?? 1 : 1)
290
+ const hasPrimarySlide = computed(() => !!currentRoute.params.no)
291
+ const currentSlideNo = computed(() => hasPrimarySlide.value ? getSlide(currentRoute.params.no as string)?.no ?? 1 : 1)
292
292
  const currentSlideRoute = computed(() => slides.value[currentSlideNo.value - 1])
293
+ const printRange = ref(parseRangeString(slides.value.length, currentRoute.query.range as string | undefined))
293
294
 
294
295
  const queryClicksRaw = useRouteQuery<string>('clicks', '0')
295
296
 
@@ -342,7 +343,7 @@ const useNavState = createSharedComposable((): SlidevContextNavState => {
342
343
 
343
344
  return {
344
345
  router,
345
- currentRoute,
346
+ currentRoute: computed(() => currentRoute),
346
347
  isPrintMode,
347
348
  isPrintWithClicks,
348
349
  isEmbedded,
@@ -356,6 +357,7 @@ const useNavState = createSharedComposable((): SlidevContextNavState => {
356
357
  clicksContext,
357
358
  queryClicksRaw,
358
359
  queryClicks,
360
+ printRange,
359
361
  getPrimaryClicks,
360
362
  }
361
363
  })
@@ -0,0 +1,28 @@
1
+ import { useStyleTag } from '@vueuse/core'
2
+ import { computed } from 'vue'
3
+ import { slideHeight, slideWidth } from '../env'
4
+ import { useNav } from './useNav'
5
+
6
+ export function usePrintStyles() {
7
+ const { isPrintMode } = useNav()
8
+
9
+ useStyleTag(computed(() => isPrintMode.value
10
+ ? `
11
+ @page {
12
+ size: ${slideWidth.value}px ${slideHeight.value}px;
13
+ margin: 0px;
14
+ }
15
+
16
+ * {
17
+ transition: none !important;
18
+ transition-duration: 0s !important;
19
+ }`
20
+ : ''))
21
+ }
22
+
23
+ // Monaco uses `<style media="screen" class="monaco-colors">` to apply colors, which will be ignored in print mode.
24
+ export function patchMonacoColors() {
25
+ document.querySelectorAll<HTMLStyleElement>('style.monaco-colors').forEach((el) => {
26
+ el.media = ''
27
+ })
28
+ }
package/constants.ts CHANGED
@@ -56,6 +56,7 @@ export const HEADMATTER_FIELDS = [
56
56
  'author',
57
57
  'keywords',
58
58
  'presenter',
59
+ 'browserExporter',
59
60
  'download',
60
61
  'exportFilename',
61
62
  'export',
@@ -0,0 +1,90 @@
1
+ <script setup lang="ts">
2
+ import { useVModel } from '@vueuse/core'
3
+ import { skipExportPdfTip } from '../state'
4
+ import Modal from './Modal.vue'
5
+
6
+ const props = defineProps({
7
+ modelValue: {
8
+ default: false,
9
+ },
10
+ })
11
+
12
+ const emit = defineEmits(['update:modelValue', 'print'])
13
+ const value = useVModel(props, 'modelValue', emit)
14
+
15
+ function print() {
16
+ value.value = false
17
+ emit('print')
18
+ }
19
+ </script>
20
+
21
+ <template>
22
+ <Modal v-model="value" class="px-6 py-4 flex flex-col gap-2">
23
+ <div class="flex gap-2 text-xl">
24
+ <div class="i-carbon:information my-auto" /> Tips
25
+ </div>
26
+ <div>
27
+ Slidev will open your browser's built-in print dialog to export the slides as PDF. <br>
28
+ In the print dialog, please:
29
+ <ul class="list-disc my-4 pl-4">
30
+ <li>
31
+ Choose "Save as PDF" as the Destination.
32
+ <span class="op-70 text-xs"> (Not "Microsoft Print to PDF") </span>
33
+ </li>
34
+ <li> Choose "Default" as the Margin. </li>
35
+ <li> Toggle on "Print backgrounds". </li>
36
+ </ul>
37
+ <div class="mb-2 op-70 text-sm">
38
+ If you're encountering problems, please try
39
+ <a href="https://sli.dev/builtin/cli#export"> the CLI </a>
40
+ or
41
+ <a href="https://github.com/slidevjs/slidev/issues/new"> open an issue</a>.
42
+ </div>
43
+ <div class="form-check op-70">
44
+ <input
45
+ v-model="skipExportPdfTip"
46
+ name="record-camera"
47
+ type="checkbox"
48
+ >
49
+ <label for="record-camera" @click="skipExportPdfTip = !skipExportPdfTip">Don't show this dialog next time.</label>
50
+ </div>
51
+ </div>
52
+ <div class="flex my-1">
53
+ <button class="cancel" @click="value = false">
54
+ Cancel
55
+ </button>
56
+ <div class="flex-auto" />
57
+ <button @click="print">
58
+ Start
59
+ </button>
60
+ </div>
61
+ </Modal>
62
+ </template>
63
+
64
+ <style scoped>
65
+ button {
66
+ @apply bg-blue-400 text-white px-4 py-1 rounded border-b-2 border-blue-600;
67
+ @apply hover:(bg-blue-500 border-blue-700);
68
+ }
69
+
70
+ button.cancel {
71
+ @apply bg-gray-400 bg-opacity-50 text-white px-4 py-1 rounded border-b-2 border-main;
72
+ @apply hover:(bg-opacity-75 border-opacity-75);
73
+ }
74
+
75
+ a {
76
+ @apply border-current border-b border-dashed hover:text-primary hover:border-solid;
77
+ }
78
+
79
+ .form-check {
80
+ @apply leading-5;
81
+
82
+ * {
83
+ @apply my-auto align-middle;
84
+ }
85
+
86
+ label {
87
+ @apply ml-1 text-sm select-none;
88
+ }
89
+ }
90
+ </style>
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ disabled?: boolean
4
+ }>()
5
+
6
+ const value = defineModel<boolean>('modelValue', {
7
+ type: Boolean,
8
+ })
9
+ </script>
10
+
11
+ <template>
12
+ <div border="~ main rounded" flex="~ gap-2 items-center" relative h-5 w-5 p0.5 hover:bg-active p1>
13
+ <div i-ri-check-line :class="value ? '' : 'op0'" />
14
+ <input v-model="value" type="checkbox" absolute inset-0 z-10 opacity-0.1 :disabled="disabled">
15
+ </div>
16
+ </template>
@@ -0,0 +1,41 @@
1
+ <script setup lang="ts">
2
+ import { Tooltip } from 'floating-vue'
3
+
4
+ defineProps<{
5
+ title: string
6
+ nested?: boolean | number
7
+ div?: boolean
8
+ description?: string
9
+ }>()
10
+
11
+ const emit = defineEmits<{
12
+ (event: 'reset'): void
13
+ }>()
14
+
15
+ function reset() {
16
+ emit('reset')
17
+ }
18
+ </script>
19
+
20
+ <template>
21
+ <component :is="div ? 'div' : 'label'" flex="~ row gap-2 items-center" select-none>
22
+ <div w-30 h-10 flex="~ gap-1 items-center">
23
+ <div
24
+ v-if="nested" i-ri-corner-down-right-line op40
25
+ :style="typeof nested === 'number' ? { marginLeft: `${nested * 0.5 + 0.5}rem` } : { marginLeft: '0.25rem' }"
26
+ />
27
+ <div v-if="!description" op75 @dblclick="reset">
28
+ {{ title }}
29
+ </div>
30
+ <Tooltip v-else distance="10">
31
+ <div op75 text-right @dblclick="reset">
32
+ {{ title }}
33
+ </div>
34
+ <template #popper>
35
+ <div text-sm min-w-90 v-html="description" />
36
+ </template>
37
+ </Tooltip>
38
+ </div>
39
+ <slot />
40
+ </component>
41
+ </template>
@@ -1,13 +1,18 @@
1
1
  <script setup lang="ts">
2
- defineProps<{
2
+ import { computed } from 'vue'
3
+
4
+ const props = defineProps<{
3
5
  title: string
4
6
  icon?: string
5
7
  as?: string
8
+ to?: string
6
9
  }>()
10
+
11
+ const type = computed(() => props.as || (props.to ? 'router-link' : 'button'))
7
12
  </script>
8
13
 
9
14
  <template>
10
- <component :is="as || 'button'" class="slidev-icon-btn" :title="title">
15
+ <component :is="type" class="slidev-icon-btn" :title="title" :to="to">
11
16
  <span class="sr-only">{{ title }}</span>
12
17
  <slot>
13
18
  <div :class="icon" />
@@ -58,7 +58,7 @@ if (__SLIDEV_FEATURE_RECORD__)
58
58
  <template>
59
59
  <nav ref="root" class="flex flex-col">
60
60
  <div
61
- class="flex flex-wrap-reverse text-xl gap-0.5 p-1 lg:gap-1 lg:p-2"
61
+ class="flex flex-wrap-reverse text-xl gap-0.5 p-1 lg:p-2"
62
62
  :class="barStyle"
63
63
  @mouseleave="onMouseLeave"
64
64
  >
@@ -131,7 +131,7 @@ if (__SLIDEV_FEATURE_RECORD__)
131
131
  <div class="i-carbon:text-annotation-toggle" />
132
132
  </IconButton>
133
133
 
134
- <IconButton v-if="isPresenter" title="Toggle Presenter Layout" class="aspect-ratio-initial" @click="togglePresenterLayout">
134
+ <IconButton v-if="isPresenter" title="Toggle Presenter Layout" class="aspect-ratio-initial flex items-center" @click="togglePresenterLayout">
135
135
  <div class="i-carbon:template" />
136
136
  {{ presenterLayout }}
137
137
  </IconButton>
@@ -142,6 +142,12 @@ if (__SLIDEV_FEATURE_RECORD__)
142
142
  </IconButton>
143
143
  </template>
144
144
 
145
+ <template v-if="__SLIDEV_FEATURE_BROWSER_EXPORTER__">
146
+ <IconButton title="Browser Exporter" to="/export">
147
+ <div class="i-carbon:document-pdf" />
148
+ </IconButton>
149
+ </template>
150
+
145
151
  <IconButton
146
152
  v-if="!isPresenter && configs.info && !isEmbedded"
147
153
  title="Show info"
@@ -153,7 +159,7 @@ if (__SLIDEV_FEATURE_RECORD__)
153
159
  <template v-if="!isPresenter && !isEmbedded">
154
160
  <MenuButton>
155
161
  <template #button>
156
- <IconButton title="Adjust settings">
162
+ <IconButton title="More Options">
157
163
  <div class="i-carbon:settings-adjust" />
158
164
  </IconButton>
159
165
  </template>
@@ -1,5 +1,4 @@
1
1
  <script setup lang="ts">
2
- import { parseRangeString } from '@slidev/parser/utils'
3
2
  import { provideLocal } from '@vueuse/core'
4
3
  import { computed } from 'vue'
5
4
  import { useNav } from '../composables/useNav'
@@ -11,7 +10,7 @@ const props = defineProps<{
11
10
  width: number
12
11
  }>()
13
12
 
14
- const { slides, currentRoute } = useNav()
13
+ const { slides, printRange } = useNav()
15
14
 
16
15
  const width = computed(() => props.width)
17
16
  const height = computed(() => props.width / slideAspect.value)
@@ -24,13 +23,6 @@ const scale = computed(() => {
24
23
  return (height.value * slideAspect.value) / slideWidth.value
25
24
  })
26
25
 
27
- // In print mode, the routes will never change. So we don't need reactivity here.
28
- let routes = slides.value
29
- if (currentRoute.value.query.range) {
30
- const r = parseRangeString(routes.length, currentRoute.value.query.range as string)
31
- routes = r.map(i => routes[i - 1])
32
- }
33
-
34
26
  const className = computed(() => ({
35
27
  'select-none': !configs.selectable,
36
28
  }))
@@ -41,18 +33,7 @@ provideLocal(injectionSlideScale, scale)
41
33
  <template>
42
34
  <div id="print-container" :class="className">
43
35
  <div id="print-content">
44
- <PrintSlide v-for="route of routes" :key="route.no" :route="route" />
36
+ <PrintSlide v-for="no of printRange" :key="no" :route="slides[no - 1]" />
45
37
  </div>
46
- <slot name="controls" />
47
38
  </div>
48
39
  </template>
49
-
50
- <style lang="postcss">
51
- #print-content {
52
- @apply bg-main;
53
- }
54
-
55
- .print-slide-container {
56
- @apply relative overflow-hidden break-after-page translate-0;
57
- }
58
- </style>
@@ -5,14 +5,14 @@ import { useFixedNav, useNav } from '../composables/useNav'
5
5
  import { CLICKS_MAX } from '../constants'
6
6
  import PrintSlideClick from './PrintSlideClick.vue'
7
7
 
8
- const { route } = defineProps<{ route: SlideRoute }>()
8
+ const { route } = defineProps<{ hidden?: boolean, route: SlideRoute }>()
9
9
  const { isPrintWithClicks } = useNav()
10
- const clicks0 = createFixedClicks(route, isPrintWithClicks.value ? 0 : CLICKS_MAX)
10
+ const clicks0 = createFixedClicks(route, () => isPrintWithClicks.value ? 0 : CLICKS_MAX)
11
11
  </script>
12
12
 
13
13
  <template>
14
14
  <PrintSlideClick
15
- :clicks-context="clicks0"
15
+ v-show="!hidden"
16
16
  :nav="useFixedNav(route, clicks0)"
17
17
  />
18
18
  <template v-if="isPrintWithClicks">
@@ -22,6 +22,7 @@ const clicks0 = createFixedClicks(route, isPrintWithClicks.value ? 0 : CLICKS_MA
22
22
  -->
23
23
  <PrintSlideClick
24
24
  v-for="i in Math.max(0, clicks0.total - clicks0.clicksStart)"
25
+ v-show="!hidden"
25
26
  :key="i"
26
27
  :nav="useFixedNav(route, createFixedClicks(route, i + clicks0.clicksStart))"
27
28
  />
@@ -2,8 +2,8 @@
2
2
  import type { SlidevContextNav } from '../composables/useNav'
3
3
  import { GlobalBottom, GlobalTop } from '#slidev/global-layers'
4
4
  import { provideLocal } from '@vueuse/core'
5
- import { computed, reactive, shallowRef } from 'vue'
6
- import { injectionSlidevContext } from '../constants'
5
+ import { computed, reactive, shallowRef, useTemplateRef } from 'vue'
6
+ import { injectionSlideElement, injectionSlidevContext } from '../constants'
7
7
  import { configs, slideHeight, slideWidth } from '../env'
8
8
  import { getSlideClass } from '../utils'
9
9
  import SlideWrapper from './SlideWrapper.vue'
@@ -32,10 +32,12 @@ provideLocal(injectionSlidevContext, reactive({
32
32
  configs,
33
33
  themeConfigs: computed(() => configs.themeConfig),
34
34
  }))
35
+
36
+ provideLocal(injectionSlideElement, useTemplateRef('slide-element'))
35
37
  </script>
36
38
 
37
39
  <template>
38
- <div :id="id" class="print-slide-container" :style="style">
40
+ <div :id="id" ref="slide-element" class="print-slide-container" :style="style">
39
41
  <GlobalBottom />
40
42
 
41
43
  <SlideWrapper
@@ -56,3 +58,9 @@ provideLocal(injectionSlidevContext, reactive({
56
58
  <GlobalTop />
57
59
  </div>
58
60
  </template>
61
+
62
+ <style scoped lang="postcss">
63
+ .print-slide-container {
64
+ @apply relative overflow-hidden break-after-page translate-0 bg-main;
65
+ }
66
+ </style>
@@ -30,8 +30,11 @@ const wakeLockItems: SelectionItem<boolean>[] = [
30
30
  </script>
31
31
 
32
32
  <template>
33
- <div class="text-sm select-none">
33
+ <div class="text-sm select-none mb-2">
34
34
  <SelectList v-model="slideScale" title="Scale" :items="scaleItems" />
35
- <SelectList v-if="__SLIDEV_FEATURE_WAKE_LOCK__ && isSupported" v-model="wakeLockEnabled" title="Wake lock" :items="wakeLockItems" />
35
+ <SelectList
36
+ v-if="__SLIDEV_FEATURE_WAKE_LOCK__ && isSupported"
37
+ v-model="wakeLockEnabled" title="Wake lock" :items="wakeLockItems"
38
+ />
36
39
  </div>
37
40
  </template>
@@ -26,6 +26,7 @@ const {
26
26
  isPrintMode,
27
27
  isPrintWithClicks,
28
28
  clicksDirection,
29
+ printRange,
29
30
  } = useNav()
30
31
 
31
32
  function preloadRoute(route: SlideRoute) {
@@ -55,7 +56,10 @@ const DrawingLayer = shallowRef<any>()
55
56
  if (__SLIDEV_FEATURE_DRAWINGS__ || __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
56
57
  import('./DrawingLayer.vue').then(v => DrawingLayer.value = v.default)
57
58
 
58
- const loadedRoutes = computed(() => slides.value.filter(r => r.meta?.__preloaded || r === currentSlideRoute.value))
59
+ const loadedRoutes = computed(() => isPrintMode.value
60
+ ? printRange.value.map(no => slides.value[no - 1])
61
+ : slides.value.filter(r => r.meta?.__preloaded || r === currentSlideRoute.value),
62
+ )
59
63
 
60
64
  function onAfterLeave() {
61
65
  // After transition, we disable it so HMR won't trigger it again
@@ -72,8 +76,8 @@ function onAfterLeave() {
72
76
 
73
77
  <!-- Slides -->
74
78
  <component
75
- :is="hasViewTransition ? 'div' : TransitionGroup"
76
- v-bind="skipTransition ? {} : currentTransition"
79
+ :is="hasViewTransition && !isPrintMode ? 'div' : TransitionGroup"
80
+ v-bind="skipTransition || isPrintMode ? {} : currentTransition"
77
81
  id="slideshow"
78
82
  tag="div"
79
83
  :class="{