@slidev/client 0.48.1 → 0.48.3

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.
@@ -60,17 +60,13 @@ watchEffect(() => {
60
60
  })
61
61
 
62
62
  onMounted(() => {
63
- if (!clicks || clicks.disabled || !props.ranges?.length)
63
+ if (!clicks || !props.ranges?.length)
64
64
  return
65
65
 
66
66
  const { start, end, delta } = clicks.resolve(props.at, props.ranges.length - 1)
67
67
  clicks.register(id, { max: end, delta })
68
68
 
69
- const index = computed(() => {
70
- if (clicks.disabled)
71
- return props.ranges.length - 1
72
- return Math.max(0, clicks.current - start + 1)
73
- })
69
+ const index = computed(() => Math.max(0, clicks.current - start + 1))
74
70
 
75
71
  const finallyRange = computed(() => {
76
72
  return props.finally === 'last' ? props.ranges.at(-1) : props.finally.toString()
@@ -55,17 +55,13 @@ onUnmounted(() => {
55
55
  })
56
56
 
57
57
  onMounted(() => {
58
- if (!clicks || clicks.disabled || !props.ranges?.length)
58
+ if (!clicks || !props.ranges?.length)
59
59
  return
60
60
 
61
61
  const { start, end, delta } = clicks.resolve(props.at, props.ranges.length - 1)
62
62
  clicks.register(id, { max: end, delta })
63
63
 
64
- const index = computed(() => {
65
- if (clicks.disabled)
66
- return props.ranges.length - 1
67
- return Math.max(0, clicks.current - start + 1)
68
- })
64
+ const index = computed(() => Math.max(0, clicks.current - start + 1))
69
65
 
70
66
  const finallyRange = computed(() => {
71
67
  return props.finally === 'last' ? props.ranges.at(-1) : props.finally.toString()
@@ -3,8 +3,9 @@ import type { RenderContext } from '@slidev/types'
3
3
  import { computed, ref } from 'vue'
4
4
  import { useElementVisibility } from '@vueuse/core'
5
5
  import { useSlideContext } from '../context'
6
+ import { useNav } from '../composables/useNav'
6
7
 
7
- type Context = 'main' | 'visible' | RenderContext
8
+ type Context = 'main' | 'visible' | 'print' | RenderContext
8
9
 
9
10
  const props = defineProps<{
10
11
  context: Context | Context[]
@@ -17,6 +18,7 @@ const targetVisible = useElementVisibility(target)
17
18
  const needsDomWrapper = Array.isArray(context) ? context.includes('visible') : context === 'visible'
18
19
 
19
20
  const { $renderContext: currentContext } = useSlideContext()
21
+ const { isPrintMode } = useNav()
20
22
  const shouldRender = computed(() => {
21
23
  const anyContext = Array.isArray(context) ? context.some(contextMatch) : contextMatch(context)
22
24
  const allConditions = Array.isArray(context) ? context.every(conditionsMatch) : conditionsMatch(context)
@@ -30,6 +32,8 @@ function contextMatch(context: Context) {
30
32
  return true
31
33
  if (context === 'visible')
32
34
  return true
35
+ if (context === 'print' && isPrintMode.value)
36
+ return true
33
37
  return false
34
38
  }
35
39
 
@@ -43,6 +47,8 @@ function conditionsMatch(context: Context) {
43
47
  <template>
44
48
  <div v-if="needsDomWrapper" ref="target">
45
49
  <slot v-if="shouldRender" />
50
+ <slot v-else name="fallback" />
46
51
  </div>
47
52
  <slot v-else-if="shouldRender" />
53
+ <slot v-else name="fallback" />
48
54
  </template>
@@ -5,6 +5,7 @@ import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
5
5
  import lz from 'lz-string'
6
6
  import { useSlideContext } from '../context'
7
7
  import { makeId, updateCodeHighlightRange } from '../logic/utils'
8
+ import { useNav } from '../composables/useNav'
8
9
 
9
10
  const props = defineProps<{
10
11
  at?: string | number
@@ -14,6 +15,7 @@ const props = defineProps<{
14
15
 
15
16
  const steps = JSON.parse(lz.decompressFromBase64(props.stepsLz)) as KeyedTokensInfo[]
16
17
  const { $clicksContext: clicks, $scale: scale } = useSlideContext()
18
+ const { isPrintMode } = useNav()
17
19
  const id = makeId()
18
20
 
19
21
  const stepIndex = ref(0)
@@ -23,11 +25,11 @@ const container = ref<HTMLElement>()
23
25
  const ranges = computed(() => props.stepRanges.map(i => i.length ? i : ['all']))
24
26
 
25
27
  onUnmounted(() => {
26
- clicks!.unregister(id)
28
+ clicks?.unregister(id)
27
29
  })
28
30
 
29
31
  onMounted(() => {
30
- if (!clicks || clicks.disabled)
32
+ if (!clicks)
31
33
  return
32
34
 
33
35
  if (ranges.value.length !== steps.length)
@@ -92,6 +94,7 @@ onMounted(() => {
92
94
  class="slidev-code relative shiki overflow-visible"
93
95
  :steps="steps"
94
96
  :step="stepIndex"
97
+ :animate="!isPrintMode"
95
98
  :options="{
96
99
  globalScale: scale,
97
100
  // TODO: make this configurable later
@@ -26,7 +26,7 @@ const matchRoute = computed(() => {
26
26
  })
27
27
 
28
28
  const matchClick = computed(() => {
29
- if (!video.value || currentContext?.value !== 'slide' || clicks?.disabled || clicks?.current === undefined)
29
+ if (!video.value || currentContext?.value !== 'slide' || !clicks)
30
30
  return false
31
31
  return clicks.map.get(video.value)?.isShown?.value ?? true
32
32
  })
@@ -14,7 +14,7 @@ const { $clicksContext: clicks } = useSlideContext()
14
14
 
15
15
  onMounted(() => {
16
16
  watchEffect((onCleanup) => {
17
- if (!clicks || clicks.disabled)
17
+ if (!clicks)
18
18
  return
19
19
 
20
20
  let delta = +props.size
@@ -8,15 +8,11 @@ import { routeForceRefresh } from '../logic/route'
8
8
  export function createClicksContextBase(
9
9
  current: Ref<number>,
10
10
  clicksOverrides?: number,
11
- isDisabled?: () => boolean,
12
11
  ): ClicksContext {
13
12
  const relativeOffsets: ClicksContext['relativeOffsets'] = new Map()
14
13
  const map: ClicksContext['map'] = shallowReactive(new Map())
15
14
 
16
15
  return {
17
- get disabled() {
18
- return isDisabled ? isDisabled() : false
19
- },
20
16
  get current() {
21
17
  return +current.value
22
18
  },
@@ -25,7 +21,7 @@ export function createClicksContextBase(
25
21
  },
26
22
  relativeOffsets,
27
23
  map,
28
- onMounted() {},
24
+ onMounted() { },
29
25
  resolve(at, size = 1) {
30
26
  const [isRelative, value] = normalizeAtProp(at)
31
27
  if (isRelative) {
@@ -170,15 +170,19 @@ export function useNavBase(
170
170
  return go(total.value)
171
171
  }
172
172
 
173
- async function go(page: number | string, clicks?: number) {
173
+ async function go(page: number | string, clicks: number = 0) {
174
174
  skipTransition.value = false
175
- await router?.push({
176
- path: getSlidePath(page, isPresenter.value),
177
- query: {
178
- ...router.currentRoute.value.query,
179
- clicks: clicks || undefined,
180
- },
181
- })
175
+ const pageChanged = currentSlideNo.value !== page
176
+ const clicksChanged = clicks !== queryClicks.value
177
+ if (pageChanged || clicksChanged) {
178
+ await router?.push({
179
+ path: getSlidePath(page, isPresenter.value),
180
+ query: {
181
+ ...router.currentRoute.value.query,
182
+ clicks: clicks === 0 ? undefined : clicks.toString(),
183
+ },
184
+ })
185
+ }
182
186
  }
183
187
 
184
188
  return {
@@ -255,8 +259,6 @@ const useNavState = createSharedComposable((): SlidevContextNavState => {
255
259
 
256
260
  const queryClicks = computed({
257
261
  get() {
258
- if (clicksContext.value.disabled)
259
- return CLICKS_MAX
260
262
  let v = +(queryClicksRaw.value || 0)
261
263
  if (Number.isNaN(v))
262
264
  v = 0
@@ -277,8 +279,6 @@ const useNavState = createSharedComposable((): SlidevContextNavState => {
277
279
  const context = createClicksContextBase(
278
280
  computed({
279
281
  get() {
280
- if (context.disabled)
281
- return CLICKS_MAX
282
282
  if (currentSlideNo.value === thisNo)
283
283
  return +(queryClicksRaw.value || 0) || 0
284
284
  else if (currentSlideNo.value > thisNo)
@@ -292,7 +292,6 @@ const useNavState = createSharedComposable((): SlidevContextNavState => {
292
292
  },
293
293
  }),
294
294
  route?.meta?.clicks,
295
- () => isPrintMode.value && !isPrintWithClicks.value,
296
295
  )
297
296
 
298
297
  // On slide mounted, make sure the query is not greater than the total
@@ -1,6 +1,7 @@
1
1
  import { ref } from 'vue'
2
2
  import { useRouter } from 'vue-router'
3
3
  import { getSlide } from '../logic/slides'
4
+ import { configs } from '../env'
4
5
 
5
6
  export function useViewTransition() {
6
7
  const router = useRouter()
@@ -16,14 +17,9 @@ export function useViewTransition() {
16
17
  const toMeta = getSlide(to.params.no as string)?.meta
17
18
  const fromNo = fromMeta?.slide?.no
18
19
  const toNo = toMeta?.slide?.no
19
- if (
20
- !(
21
- fromNo !== undefined && toNo !== undefined && (
22
- (fromMeta?.transition === 'view-transition' && fromNo < toNo)
23
- || (toMeta?.transition === 'view-transition' && toNo < fromNo)
24
- )
25
- )
26
- ) {
20
+ const transitionType = fromNo != null && toNo != null
21
+ && ((fromNo < toNo ? fromMeta?.transition : toMeta?.transition) ?? configs.transition)
22
+ if (transitionType !== 'view-transition') {
27
23
  isViewTransition.value = false
28
24
  return
29
25
  }
@@ -43,17 +39,15 @@ export function useViewTransition() {
43
39
  let changeRoute: () => void
44
40
  const ready = new Promise<void>(resolve => (changeRoute = resolve))
45
41
 
46
- // eslint-disable-next-line ts/ban-ts-comment
47
- // @ts-expect-error
48
- const transition = document.startViewTransition(() => {
49
- changeRoute()
50
- return promise
51
- })
42
+ // Wait for `TransitionGroup` to become normal `div`
43
+ setTimeout(() => {
44
+ // @ts-expect-error missing types
45
+ document.startViewTransition(() => {
46
+ changeRoute()
47
+ return promise
48
+ })
49
+ }, 50)
52
50
 
53
- transition.finished.then(() => {
54
- viewTransitionAbort = undefined
55
- viewTransitionFinish = undefined
56
- })
57
51
  return ready
58
52
  })
59
53
 
@@ -28,21 +28,21 @@ function onMousedown() {
28
28
 
29
29
  <template>
30
30
  <div
31
- class="flex gap-0.5 items-center select-none"
31
+ class="flex gap-1 items-center select-none"
32
32
  :title="`Clicks in this slide: ${total}`"
33
33
  :class="total ? '' : 'op50'"
34
34
  >
35
- <div class="flex gap-1 items-center min-w-16 tabular-nums">
35
+ <div class="flex gap-0.5 items-center min-w-16 font-mono mr1">
36
36
  <carbon:cursor-1 text-sm op50 />
37
+ <div flex-auto />
37
38
  <template v-if="current >= 0 && current !== CLICKS_MAX">
38
- <div flex-auto />
39
39
  <span text-primary>{{ current }}</span>
40
40
  <span op25>/</span>
41
41
  </template>
42
42
  <span op50>{{ total }}</span>
43
43
  </div>
44
44
  <div
45
- relative flex-auto h5 flex="~"
45
+ relative flex-auto h5 font-mono flex="~"
46
46
  @dblclick="current = clicksContext.total"
47
47
  >
48
48
  <div
@@ -54,7 +54,7 @@ function onMousedown() {
54
54
  ]"
55
55
  :style="{ width: total > 0 ? `${1 / total * 100}%` : '100%' }"
56
56
  >
57
- <div absolute inset-0 :class="i <= current ? 'bg-primary op20' : ''" />
57
+ <div absolute inset-0 :class="i <= current ? 'bg-primary op15' : ''" />
58
58
  <div
59
59
  :class="[
60
60
  +i === +current ? 'text-primary font-bold op100 border-primary' : 'op30 border-main',
@@ -1,11 +1,13 @@
1
1
  <script setup lang="ts">
2
2
  import type { SlideRoute } from '@slidev/types'
3
- import { useFixedNav } from '../composables/useNav'
3
+ import { useFixedNav, useNav } from '../composables/useNav'
4
4
  import { createFixedClicks } from '../composables/useClicks'
5
+ import { CLICKS_MAX } from '../constants'
5
6
  import PrintSlideClick from './PrintSlideClick.vue'
6
7
 
7
8
  const { route } = defineProps<{ route: SlideRoute }>()
8
- const clicks0 = createFixedClicks(route, 0)
9
+ const { isPrintWithClicks } = useNav()
10
+ const clicks0 = createFixedClicks(route, isPrintWithClicks.value ? 0 : CLICKS_MAX)
9
11
  </script>
10
12
 
11
13
  <template>
@@ -13,7 +15,7 @@ const clicks0 = createFixedClicks(route, 0)
13
15
  :clicks-context="clicks0"
14
16
  :nav="useFixedNav(route, clicks0)"
15
17
  />
16
- <template v-if="!clicks0.disabled">
18
+ <template v-if="isPrintWithClicks">
17
19
  <PrintSlideClick
18
20
  v-for="i of clicks0.total"
19
21
  :key="i"
@@ -133,7 +133,6 @@ watchEffect(() => {
133
133
  <SlideContainer
134
134
  :key="route.no"
135
135
  :width="cardWidth"
136
- :clicks-disabled="true"
137
136
  class="pointer-events-none"
138
137
  >
139
138
  <SlideWrapper
package/logic/route.ts CHANGED
@@ -22,6 +22,9 @@ export function useRouteQuery<T extends string | string[]>(
22
22
  },
23
23
  set(v) {
24
24
  nextTick(() => {
25
+ const oldValue = router.currentRoute.value.query[name]
26
+ if ((oldValue ?? defaultValue?.toString()) === v.toString())
27
+ return
25
28
  router[unref(mode) as 'replace' | 'push']({
26
29
  query: {
27
30
  ...router.currentRoute.value.query,
@@ -30,7 +33,7 @@ export function useRouteQuery<T extends string | string[]>(
30
33
  })
31
34
  })
32
35
  },
33
- }) as any
36
+ })
34
37
  }
35
38
 
36
39
  // force update collected elements when the route is fully resolved
@@ -9,7 +9,7 @@ const transitionResolveMap: Record<string, string | undefined> = {
9
9
  'slide-down': 'slide-down | slide-up',
10
10
  }
11
11
 
12
- export function resolveTransition(transition?: string | TransitionGroupProps, isBackward = false): TransitionGroupProps | undefined {
12
+ function resolveTransition(transition?: string | TransitionGroupProps, isBackward = false): TransitionGroupProps | undefined {
13
13
  if (!transition)
14
14
  return undefined
15
15
  if (typeof transition === 'string') {
@@ -132,7 +132,7 @@ function isCurrent(thisClick: number | [number, number], clicks: number) {
132
132
  export function resolveClick(el: Element, dir: DirectiveBinding<any>, value: VClickValue, clickAfter = false, flagHide = false): ResolvedClicksInfo | null {
133
133
  const ctx = dirInject(dir, injectionClicksContext)?.value
134
134
 
135
- if (!el || !ctx || ctx.disabled)
135
+ if (!el || !ctx)
136
136
  return null
137
137
 
138
138
  if (value === false || value === 'false')
package/modules/v-mark.ts CHANGED
@@ -116,7 +116,8 @@ export function createVMarkDirective() {
116
116
 
117
117
  const resolvedClick = resolveClick(el, binding, options.value.at)
118
118
  if (!resolvedClick) {
119
- console.error('[Slidev] Invalid value for v-mark:', options.value.at)
119
+ // No click animation, just show the mark
120
+ annotation.show()
120
121
  return
121
122
  }
122
123
 
@@ -130,19 +131,12 @@ export function createVMarkDirective() {
130
131
 
131
132
  const at = options.value.at
132
133
 
133
- if (at === true) {
134
+ if (at === true)
134
135
  shouldShow = true
135
- }
136
- else if (at === false) {
136
+ else if (at === false)
137
137
  shouldShow = false
138
- }
139
- else if (resolvedClick) {
138
+ else
140
139
  shouldShow = resolvedClick.isActive.value
141
- }
142
- else {
143
- console.error('[Slidev] Invalid value for v-mark:', at)
144
- return
145
- }
146
140
 
147
141
  if (shouldShow == null)
148
142
  return
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@slidev/client",
3
3
  "type": "module",
4
- "version": "0.48.1",
4
+ "version": "0.48.3",
5
5
  "description": "Presentation slides for developers",
6
6
  "author": "antfu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -36,7 +36,7 @@
36
36
  "@shikijs/vitepress-twoslash": "^1.1.7",
37
37
  "@slidev/rough-notation": "^0.1.0",
38
38
  "@typescript/ata": "^0.9.4",
39
- "@unhead/vue": "^1.8.11",
39
+ "@unhead/vue": "^1.8.18",
40
40
  "@unocss/reset": "^0.58.5",
41
41
  "@vueuse/core": "^10.9.0",
42
42
  "@vueuse/math": "^10.9.0",
@@ -50,7 +50,7 @@
50
50
  "katex": "^0.16.9",
51
51
  "lz-string": "^1.5.0",
52
52
  "mermaid": "^10.9.0",
53
- "monaco-editor": "^0.46.0",
53
+ "monaco-editor": "^0.47.0",
54
54
  "prettier": "^3.2.5",
55
55
  "recordrtc": "^5.6.2",
56
56
  "shiki": "^1.1.7",
@@ -60,10 +60,10 @@
60
60
  "vue": "^3.4.21",
61
61
  "vue-demi": "^0.14.7",
62
62
  "vue-router": "^4.3.0",
63
- "@slidev/parser": "0.48.1",
64
- "@slidev/types": "0.48.1"
63
+ "@slidev/types": "0.48.3",
64
+ "@slidev/parser": "0.48.3"
65
65
  },
66
66
  "devDependencies": {
67
- "vite": "^5.1.5"
67
+ "vite": "^5.1.6"
68
68
  }
69
69
  }
@@ -100,37 +100,42 @@ onMounted(() => {
100
100
 
101
101
  <template>
102
102
  <div class="h-screen w-screen of-hidden flex">
103
- <nav class="h-full flex flex-col border-r border-main p2 select-none">
104
- <div class="flex flex-col flex-auto items-center justify-center group gap-1">
105
- <div
106
- v-for="(route, idx) of slides"
107
- :key="route.no"
108
- class="relative"
109
- >
110
- <button
111
- class="relative transition duration-300 w-8 h-8 rounded hover:bg-active hover:op100"
112
- :class="activeBlocks.includes(idx) ? 'op100 text-primary bg-gray:5' : 'op20'"
113
- @click="scrollToSlide(idx)"
114
- >
115
- <div>{{ idx + 1 }}</div>
116
- </button>
103
+ <nav class="grid grid-rows-[auto_max-content] border-r border-main select-none max-h-full h-full">
104
+ <div class="relative">
105
+ <div class="absolute left-0 top-0 bottom-0 w-200 flex flex-col flex-auto items-end group p2 gap-1 max-h-full of-scroll" style="direction:rtl">
117
106
  <div
118
- v-if="route.meta?.slide?.title"
119
- class="pointer-events-none select-none absolute left-110% bg-main top-50% translate-y--50% ws-nowrap z-10 px2 shadow-xl rounded border border-main transition duration-400 op0 group-hover:op100"
120
- :class="activeBlocks.includes(idx) ? 'text-primary' : 'text-main important-text-op-50'"
107
+ v-for="(route, idx) of slides"
108
+ :key="route.no"
109
+ class="relative"
110
+ style="direction:ltr"
121
111
  >
122
- {{ route.meta?.slide?.title }}
112
+ <button
113
+ class="relative transition duration-300 w-8 h-8 rounded hover:bg-active hover:op100"
114
+ :class="activeBlocks.includes(idx) ? 'op100 text-primary bg-gray:5' : 'op20'"
115
+ @click="scrollToSlide(idx)"
116
+ >
117
+ <div>{{ idx + 1 }}</div>
118
+ </button>
119
+ <div
120
+ v-if="route.meta?.slide?.title"
121
+ class="pointer-events-none select-none absolute left-110% backdrop-blur-8 top-50% translate-y--50% ws-nowrap z-10 px2 shadow-xl rounded border border-main transition duration-400 op0 group-hover:op100"
122
+ :class="activeBlocks.includes(idx) ? 'text-primary' : 'text-main important-text-op-50'"
123
+ >
124
+ {{ route.meta?.slide?.title }}
125
+ </div>
123
126
  </div>
124
127
  </div>
125
128
  </div>
126
- <IconButton
127
- v-if="!isColorSchemaConfigured"
128
- :title="isDark ? 'Switch to light mode theme' : 'Switch to dark mode theme'"
129
- @click="toggleDark()"
130
- >
131
- <carbon-moon v-if="isDark" />
132
- <carbon-sun v-else />
133
- </IconButton>
129
+ <div p2 border="t main">
130
+ <IconButton
131
+ v-if="!isColorSchemaConfigured"
132
+ :title="isDark ? 'Switch to light mode theme' : 'Switch to dark mode theme'"
133
+ @click="toggleDark()"
134
+ >
135
+ <carbon-moon v-if="isDark" />
136
+ <carbon-sun v-else />
137
+ </IconButton>
138
+ </div>
134
139
  </nav>
135
140
  <main
136
141
  class="flex-1 h-full of-auto"
@@ -172,7 +177,6 @@ onMounted(() => {
172
177
  <SlideContainer
173
178
  :key="route.no"
174
179
  :width="cardWidth"
175
- :clicks-disabled="true"
176
180
  class="pointer-events-none important:[&_*]:select-none"
177
181
  >
178
182
  <SlideWrapper