Skip to content

Commit edb2399

Browse files
authored
fix(client): guard useNav against a missing injection context (#2694)
1 parent e0422d6 commit edb2399

2 files changed

Lines changed: 18 additions & 2 deletions

File tree

packages/client/composables/useNav.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,14 @@ describe('useNavBase', () => {
127127
expect(observedNav?.currentSlideRoute.value.meta.slide.frontmatter.title).toBe('Second')
128128
expect(observedNav?.tocTree.value[1].active).toBe(true)
129129
})
130+
131+
it('falls back to the shared nav when there is no injection context', () => {
132+
// Directive hooks such as `v-motion`'s and `v-mark`'s `mounted` call
133+
// `useNav()` while no component instance is current. There is no
134+
// slide-local context to read there, so the shared nav is the answer.
135+
vi.stubGlobal('location', { search: '' })
136+
137+
expect(() => useNav()).not.toThrow()
138+
expect(useNav().currentSlideNo.value).toBe(1)
139+
})
130140
})

packages/client/composables/useNav.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { RouteLocationNormalized, Router } from 'vue-router'
44
import { clamp } from '@antfu/utils'
55
import { parseRangeString } from '@slidev/parser/utils'
66
import { createSharedComposable, injectLocal } from '@vueuse/core'
7-
import { computed, ref, toRaw, watch } from 'vue'
7+
import { computed, hasInjectionContext, ref, toRaw, watch } from 'vue'
88
import { useRoute, useRouter } from 'vue-router'
99
import { slides } from '#slidev/slides'
1010
import { CLICKS_MAX, injectionSlidevContext } from '../constants'
@@ -404,7 +404,13 @@ const useSharedNav = createSharedComposable((): SlidevContextNavFull => {
404404

405405
export function useNav(): SlidevContextNavFull {
406406
const nav = useSharedNav()
407-
const context = injectLocal(injectionSlidevContext, undefined)
407+
// `useNav()` is also called outside of `setup()`, most notably from the
408+
// `mounted`/`created` hooks of the `v-motion` and `v-mark` directives.
409+
// `injectLocal` throws there, and there is no slide-local context to read
410+
// anyway, so fall back to the shared nav.
411+
const context = hasInjectionContext()
412+
? injectLocal(injectionSlidevContext, undefined)
413+
: undefined
408414
if (!context)
409415
return nav
410416

0 commit comments

Comments
 (0)