@slidev/client 0.47.5 → 0.48.0-beta.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.
@@ -2,7 +2,7 @@
2
2
  import { useHead } from '@unhead/vue'
3
3
  import { computed, onMounted, reactive, ref, shallowRef, watch } from 'vue'
4
4
  import { useMouse, useWindowFocus } from '@vueuse/core'
5
- import { clicks, clicksTotal, currentPage, currentRoute, hasNext, nextRoute, total, useSwipeControls } from '../logic/nav'
5
+ import { clicksContext, currentPage, currentRoute, hasNext, nextRoute, queryClicks, rawRoutes, total, useSwipeControls } from '../logic/nav'
6
6
  import { decreasePresenterFontSize, increasePresenterFontSize, presenterLayout, presenterNotesFontSize, showEditor, showOverview, showPresenterCursor } from '../state'
7
7
  import { configs, themeVars } from '../env'
8
8
  import { sharedState } from '../state/shared'
@@ -10,6 +10,7 @@ import { registerShortcuts } from '../logic/shortcuts'
10
10
  import { getSlideClass } from '../utils'
11
11
  import { useTimer } from '../logic/utils'
12
12
  import { isDrawing } from '../logic/drawings'
13
+ import { useFixedClicks } from '../composables/useClicks'
13
14
  import SlideContainer from './SlideContainer.vue'
14
15
  import NavControls from './NavControls.vue'
15
16
  import SlidesOverview from './SlidesOverview.vue'
@@ -35,26 +36,21 @@ const notesEditing = ref(false)
35
36
 
36
37
  const { timer, resetTimer } = useTimer()
37
38
 
38
- const nextTabElements = ref([])
39
- const nextSlide = computed(() => {
40
- if (clicks.value < clicksTotal.value) {
41
- return {
42
- route: currentRoute.value,
43
- clicks: clicks.value + 1,
44
- }
45
- }
46
- else {
47
- if (hasNext.value) {
48
- return {
49
- route: nextRoute.value,
50
- clicks: 0,
51
- }
52
- }
53
- else {
54
- return null
55
- }
56
- }
39
+ const clicksCtxMap = rawRoutes.map(route => useFixedClicks(route))
40
+ const nextFrame = computed(() => {
41
+ if (clicksContext.value.current < clicksContext.value.total)
42
+ return [currentRoute.value!, clicksContext.value.current + 1] as const
43
+ else if (hasNext.value)
44
+ return [nextRoute.value!, 0] as const
45
+ else
46
+ return null
47
+ })
48
+ const nextFrameClicksCtx = computed(() => {
49
+ return nextFrame.value && clicksCtxMap[+nextFrame.value[0].path - 1]
57
50
  })
51
+ watch([currentRoute, queryClicks], () => {
52
+ nextFrameClicksCtx.value && (nextFrameClicksCtx.value[0].value = nextFrame.value![1])
53
+ }, { immediate: true })
58
54
 
59
55
  const Editor = shallowRef<any>()
60
56
  if (__DEV__ && __SLIDEV_FEATURE_EDITOR__)
@@ -120,17 +116,16 @@ onMounted(() => {
120
116
  </div>
121
117
  <div class="relative grid-section next flex flex-col p-2 lg:p-4" :style="themeVars">
122
118
  <SlideContainer
123
- v-if="nextSlide"
119
+ v-if="nextFrame && nextFrameClicksCtx"
124
120
  key="next"
125
121
  class="h-full w-full"
126
122
  >
127
123
  <SlideWrapper
128
- :is="nextSlide.route?.component as any"
129
- v-model:clicks-elements="nextTabElements"
130
- :clicks="nextSlide.clicks"
131
- :clicks-disabled="false"
132
- :class="getSlideClass(nextSlide.route)"
133
- :route="nextSlide.route"
124
+ :is="nextFrame[0].component as any"
125
+ :key="nextFrame[0].path"
126
+ :clicks-context="nextFrameClicksCtx[1]"
127
+ :class="getSlideClass(nextFrame[0])"
128
+ :route="nextFrame[0]"
134
129
  render-context="previewNext"
135
130
  />
136
131
  </SlideContainer>
@@ -1,22 +1,24 @@
1
1
  <script setup lang="ts">
2
2
  import type { RouteRecordRaw } from 'vue-router'
3
- import { computed, ref } from 'vue'
3
+ import { computed } from 'vue'
4
4
  import { useNav } from '../composables/useNav'
5
- import { isClicksDisabled } from '../logic/nav'
5
+ import { useFixedClicks } from '../composables/useClicks'
6
6
  import PrintSlideClick from './PrintSlideClick.vue'
7
7
 
8
8
  const props = defineProps<{ route: RouteRecordRaw }>()
9
9
 
10
- const clicksElements = ref(props.route.meta?.__clicksElements || [])
11
- const clicks = computed(() => props.route.meta?.clicks ?? clicksElements.value.length)
12
-
13
10
  const route = computed(() => props.route)
14
11
  const nav = useNav(route)
12
+ const clicks0 = useFixedClicks(route.value, 0)[1]
15
13
  </script>
16
14
 
17
15
  <template>
18
- <PrintSlideClick v-model:clicks-elements="clicksElements" :clicks="0" :nav="nav" :route="route" />
19
- <template v-if="!isClicksDisabled">
20
- <PrintSlideClick v-for="i of clicks" :key="i" :clicks="i" :nav="nav" :route="route" />
16
+ <PrintSlideClick
17
+ :clicks-context="clicks0"
18
+ :nav="nav"
19
+ :route="route"
20
+ />
21
+ <template v-if="!clicks0.disabled">
22
+ <PrintSlideClick v-for="i of clicks0.total" :key="i" :clicks-context="useFixedClicks(route, i)[1]" :nav="nav" :route="route" />
21
23
  </template>
22
24
  </template>
@@ -1,10 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import type { RouteRecordRaw } from 'vue-router'
3
3
  import { computed, provide, reactive, shallowRef } from 'vue'
4
- import { useVModel } from '@vueuse/core'
5
- import { useNavClicks } from '../composables/useNavClicks'
4
+ import type { ClicksContext } from '@slidev/types'
6
5
  import { injectionSlidevContext } from '../constants'
7
- import { isClicksDisabled } from '../logic/nav'
8
6
  import { configs, slideHeight, slideWidth } from '../env'
9
7
  import { getSlideClass } from '../utils'
10
8
  import type { SlidevContextNav } from '../modules/context'
@@ -17,16 +15,11 @@ import GlobalTop from '/@slidev/global-components/top'
17
15
  import GlobalBottom from '/@slidev/global-components/bottom'
18
16
 
19
17
  const props = defineProps<{
20
- clicks: number
21
- clicksElements?: HTMLElement[]
18
+ clicksContext: ClicksContext
22
19
  nav: SlidevContextNav
23
20
  route: RouteRecordRaw
24
21
  }>()
25
22
 
26
- const emit = defineEmits(['update:clicksElements'])
27
-
28
- const clicksElements = useVModel(props, 'clicksElements', emit)
29
-
30
23
  const style = computed(() => ({
31
24
  height: `${slideHeight}px`,
32
25
  width: `${slideWidth}px`,
@@ -36,12 +29,12 @@ const DrawingPreview = shallowRef<any>()
36
29
  if (__SLIDEV_FEATURE_DRAWINGS__ || __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
37
30
  import('./DrawingPreview.vue').then(v => (DrawingPreview.value = v.default))
38
31
 
39
- const clicks = computed(() => props.clicks)
40
- const navClicks = useNavClicks(clicks, props.nav.currentRoute, props.nav.currentPage)
41
- const id = computed(() => `${props.route.path.toString().padStart(3, '0')}-${(clicks.value + 1).toString().padStart(2, '0')}`)
32
+ const id = computed(() =>
33
+ `${props.route.path.toString().padStart(3, '0')}-${(props.nav.clicks.value + 1).toString().padStart(2, '0')}`,
34
+ )
42
35
 
43
36
  provide(injectionSlidevContext, reactive({
44
- nav: { ...props.nav, ...navClicks },
37
+ nav: props.nav,
45
38
  configs,
46
39
  themeConfigs: computed(() => configs.themeConfig),
47
40
  }))
@@ -53,9 +46,7 @@ provide(injectionSlidevContext, reactive({
53
46
 
54
47
  <SlideWrapper
55
48
  :is="route?.component!"
56
- v-model:clicks-elements="clicksElements"
57
- :clicks="isClicksDisabled ? undefined : clicks"
58
- :clicks-disabled="isClicksDisabled"
49
+ :clicks-context="clicksContext"
59
50
  :class="getSlideClass(route)"
60
51
  :route="route"
61
52
  />
@@ -1,26 +1,14 @@
1
- import { useVModel } from '@vueuse/core'
2
- import { computed, defineComponent, h, provide, ref, toRef } from 'vue'
3
- import type { RenderContext } from '@slidev/types'
4
- import { injectionActive, injectionClicks, injectionClicksDisabled, injectionClicksElements, injectionCurrentPage, injectionOrderMap, injectionRenderContext, injectionRoute } from '../constants'
1
+ import { defineComponent, h, provide, ref, toRef } from 'vue'
2
+ import type { PropType } from 'vue'
3
+ import type { ClicksContext, RenderContext } from '@slidev/types'
4
+ import { injectionActive, injectionClicksContext, injectionCurrentPage, injectionRenderContext, injectionRoute } from '../constants'
5
5
 
6
6
  export default defineComponent({
7
7
  name: 'SlideWrapper',
8
8
  props: {
9
- clicks: {
10
- type: [Number, String],
11
- default: 0,
12
- },
13
- clicksElements: {
14
- type: Array,
15
- default: () => [] as Element[],
16
- },
17
- clicksOrderMap: {
18
- type: Map,
19
- default: () => new Map<number, HTMLElement[]>(),
20
- },
21
- clicksDisabled: {
22
- type: Boolean,
23
- default: false,
9
+ clicksContext: {
10
+ type: Object as PropType<ClicksContext>,
11
+ required: true,
24
12
  },
25
13
  renderContext: {
26
14
  type: String,
@@ -39,33 +27,12 @@ export default defineComponent({
39
27
  default: undefined,
40
28
  },
41
29
  },
42
- setup(props, { emit }) {
43
- const clicks = useVModel(props, 'clicks', emit)
44
- const clicksElements = useVModel(props, 'clicksElements', emit)
45
- const clicksDisabled = useVModel(props, 'clicksDisabled', emit)
46
- const clicksOrderMap = useVModel(props, 'clicksOrderMap', emit)
47
-
48
- clicksElements.value.length = 0
49
-
50
- const clicksWithDisable = computed({
51
- get() {
52
- if (clicksDisabled.value)
53
- return 9999999
54
- return +clicks.value
55
- },
56
- set(value) {
57
- clicks.value = value
58
- },
59
- })
60
-
30
+ setup(props) {
61
31
  provide(injectionRoute, props.route as any)
62
32
  provide(injectionCurrentPage, ref(+props.route?.path))
63
33
  provide(injectionRenderContext, ref(props.renderContext as RenderContext))
64
34
  provide(injectionActive, toRef(props, 'active'))
65
- provide(injectionClicks, clicksWithDisable)
66
- provide(injectionClicksDisabled, clicksDisabled)
67
- provide(injectionClicksElements, clicksElements as any)
68
- provide(injectionOrderMap, clicksOrderMap as any)
35
+ provide(injectionClicksContext, toRef(props, 'clicksContext'))
69
36
  },
70
37
  render() {
71
38
  if (this.$props.is)
@@ -5,6 +5,7 @@ import { themeVars } from '../env'
5
5
  import { breakpoints, showOverview, windowSize } from '../state'
6
6
  import { currentPage, go as goSlide, rawRoutes } from '../logic/nav'
7
7
  import { currentOverviewPage, overviewRowCount } from '../logic/overview'
8
+ import { useFixedClicks } from '../composables/useClicks'
8
9
  import { getSlideClass } from '../utils'
9
10
  import SlideContainer from './SlideContainer.vue'
10
11
  import SlideWrapper from './SlideWrapper'
@@ -138,7 +139,7 @@ watchEffect(() => {
138
139
  <SlideWrapper
139
140
  :is="route.component"
140
141
  v-if="route?.component"
141
- :clicks-disabled="true"
142
+ :clicks-context="useFixedClicks(route, 99999)[1]"
142
143
  :class="getSlideClass(route)"
143
144
  :route="route"
144
145
  render-context="overview"
@@ -1,9 +1,10 @@
1
1
  <script setup lang="ts">
2
2
  import { TransitionGroup, computed, shallowRef, watch } from 'vue'
3
- import { clicks, currentRoute, isPresenter, nextRoute, rawRoutes, router, transition } from '../logic/nav'
3
+ import { currentRoute, isPresenter, nextRoute, rawRoutes, transition } from '../logic/nav'
4
4
  import { getSlideClass } from '../utils'
5
5
  import { useViewTransition } from '../composables/useViewTransition'
6
6
  import { skipTransition } from '../composables/hmr'
7
+ import { usePrimaryClicks } from '../composables/useClicks'
7
8
  import SlideWrapper from './SlideWrapper'
8
9
 
9
10
  // @ts-expect-error virtual module
@@ -27,12 +28,6 @@ watch(currentRoute, () => {
27
28
 
28
29
  const hasViewTransition = useViewTransition()
29
30
 
30
- // preserve the clicks count for previous slide to avoid flash on transition
31
- let previousClicks: [string | undefined, number] = [] as any
32
- router.beforeEach(() => {
33
- previousClicks = [currentRoute.value?.path, clicks.value]
34
- })
35
-
36
31
  const DrawingLayer = shallowRef<any>()
37
32
  if (__SLIDEV_FEATURE_DRAWINGS__ || __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
38
33
  import('./DrawingLayer.vue').then(v => DrawingLayer.value = v.default)
@@ -62,9 +57,7 @@ function onAfterLeave() {
62
57
  <SlideWrapper
63
58
  :is="route?.component as any"
64
59
  v-show="route === currentRoute"
65
- :clicks="route === currentRoute ? clicks : route.path === previousClicks[0] ? previousClicks[1] : 0"
66
- :clicks-elements="route.meta?.__clicksElements || []"
67
- :clicks-disabled="false"
60
+ :clicks-context="usePrimaryClicks(route)"
68
61
  :class="getSlideClass(route)"
69
62
  :route="route"
70
63
  :render-context="renderContext"
package/logic/nav.ts CHANGED
@@ -6,19 +6,21 @@ import { timestamp, usePointerSwipe } from '@vueuse/core'
6
6
  import { rawRoutes, router } from '../routes'
7
7
  import { configs } from '../env'
8
8
  import { skipTransition } from '../composables/hmr'
9
+ import { usePrimaryClicks } from '../composables/useClicks'
9
10
  import { useRouteQuery } from './route'
10
11
  import { isDrawing } from './drawings'
11
12
 
12
13
  export { rawRoutes, router }
13
14
 
14
15
  // force update collected elements when the route is fully resolved
15
- const routeForceRefresh = ref(0)
16
+ export const routeForceRefresh = ref(0)
16
17
  nextTick(() => {
17
18
  router.afterEach(async () => {
18
19
  await nextTick()
19
20
  routeForceRefresh.value += 1
20
21
  })
21
22
  })
23
+
22
24
  export const navDirection = ref(0)
23
25
 
24
26
  export const route = computed(() => router.currentRoute.value)
@@ -28,11 +30,25 @@ export const isPrintWithClicks = computed(() => route.value.query.print === 'cli
28
30
  export const isEmbedded = computed(() => route.value.query.embedded !== undefined)
29
31
  export const isPresenter = computed(() => route.value.path.startsWith('/presenter'))
30
32
  export const isNotesViewer = computed(() => route.value.path.startsWith('/notes'))
31
- export const isClicksDisabled = computed(() => isPrintMode.value && !isPrintWithClicks.value)
32
33
  export const presenterPassword = computed(() => route.value.query.password)
33
34
  export const showPresenter = computed(() => !isPresenter.value && (!configs.remote || presenterPassword.value === configs.remote))
34
35
 
35
- export const queryClicks = useRouteQuery('clicks', '0')
36
+ const queryClicksRaw = useRouteQuery('clicks', '0')
37
+ export const queryClicks = computed({
38
+ get() {
39
+ // eslint-disable-next-line ts/no-use-before-define
40
+ if (clicksContext.value.disabled)
41
+ return 99999
42
+ let v = +(queryClicksRaw.value || 0)
43
+ if (Number.isNaN(v))
44
+ v = 0
45
+ return v
46
+ },
47
+ set(v) {
48
+ queryClicksRaw.value = v.toString()
49
+ },
50
+ })
51
+
36
52
  export const total = computed(() => rawRoutes.length)
37
53
  export const path = computed(() => route.value.path)
38
54
 
@@ -45,27 +61,9 @@ export const currentLayout = computed(() => currentRoute.value?.meta?.layout ||
45
61
  export const nextRoute = computed(() => rawRoutes.find(i => i.path === `${Math.min(rawRoutes.length, currentPage.value + 1)}`))
46
62
  export const prevRoute = computed(() => rawRoutes.find(i => i.path === `${Math.max(1, currentPage.value - 1)}`))
47
63
 
48
- export const clicksElements = computed<HTMLElement[]>(() => {
49
- // eslint-disable-next-line no-unused-expressions
50
- routeForceRefresh.value
51
- return currentRoute.value?.meta?.__clicksElements || []
52
- })
53
-
54
- export const clicks = computed<number>({
55
- get() {
56
- if (isClicksDisabled.value)
57
- return 99999
58
- let clicks = +(queryClicks.value || 0)
59
- if (Number.isNaN(clicks))
60
- clicks = 0
61
- return clicks
62
- },
63
- set(v) {
64
- queryClicks.value = v.toString()
65
- },
66
- })
67
-
68
- export const clicksTotal = computed(() => +(currentRoute.value?.meta?.clicks ?? clicksElements.value.length))
64
+ export const clicksContext = computed(() => usePrimaryClicks(currentRoute.value))
65
+ export const clicks = computed(() => clicksContext.value.current)
66
+ export const clicksTotal = computed(() => clicksContext.value.total)
69
67
 
70
68
  export const hasNext = computed(() => currentPage.value < rawRoutes.length || clicks.value < clicksTotal.value)
71
69
  export const hasPrev = computed(() => currentPage.value > 1 || clicks.value > 0)
@@ -86,17 +84,17 @@ watch(currentRoute, (next, prev) => {
86
84
  })
87
85
 
88
86
  export function next() {
89
- if (clicksTotal.value <= clicks.value)
87
+ if (clicksTotal.value <= queryClicks.value)
90
88
  nextSlide()
91
89
  else
92
- clicks.value += 1
90
+ queryClicks.value += 1
93
91
  }
94
92
 
95
93
  export async function prev() {
96
- if (clicks.value <= 0)
94
+ if (queryClicks.value <= 0)
97
95
  await prevSlide()
98
96
  else
99
- clicks.value -= 1
97
+ queryClicks.value -= 1
100
98
  }
101
99
 
102
100
  export function getPath(no: number | string) {
package/logic/utils.ts CHANGED
@@ -30,3 +30,21 @@ export function makeId(length = 5) {
30
30
  result.push(characters.charAt(Math.floor(Math.random() * charactersLength)))
31
31
  return result.join('')
32
32
  }
33
+
34
+ /**
35
+ * '+3' => '+3'
36
+ * '-3' => '-3'
37
+ * '3' => 3
38
+ * 3 => 3
39
+ */
40
+ export function normalizeAtProp(at: string | number = '+1'): [isRelative: boolean, value: number] {
41
+ let n = +at
42
+ if (Number.isNaN(n)) {
43
+ console.warn('[slidev] Invalid click position:', at)
44
+ n = 0
45
+ }
46
+ return [
47
+ typeof at === 'string' && '+-'.includes(at[0]),
48
+ n,
49
+ ]
50
+ }
@@ -4,21 +4,19 @@ import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
4
4
  import type { ComputedRef } from '@vue/reactivity'
5
5
  import type { configs } from '../env'
6
6
  import * as nav from '../logic/nav'
7
- import { clicks, route } from '../logic/nav'
7
+ import { route } from '../logic/nav'
8
8
  import { isDark } from '../logic/dark'
9
- import { injectionClicks, injectionCurrentPage, injectionSlidevContext } from '../constants'
9
+ import { injectionCurrentPage, injectionSlidevContext } from '../constants'
10
10
  import { useContext } from '../composables/useContext'
11
11
 
12
- export type SlidevContextNavKey = 'path' | 'total' | 'currentPage' | 'currentPath' | 'currentRoute' | 'currentSlideId' | 'currentLayout' | 'nextRoute' | 'rawTree' | 'treeWithActiveStatuses' | 'tree' | 'downloadPDF' | 'next' | 'nextSlide' | 'openInEditor' | 'prev' | 'prevSlide' | 'rawRoutes' | 'go'
13
- export type SlidevContextNavClicksKey = 'clicks' | 'clicksElements' | 'clicksTotal' | 'hasNext' | 'hasPrev'
12
+ export type SlidevContextNavKey = 'path' | 'total' | 'clicksContext' | 'clicks' | 'clicksTotal' | 'currentPage' | 'currentPath' | 'currentRoute' | 'currentSlideId' | 'currentLayout' | 'nextRoute' | 'rawTree' | 'treeWithActiveStatuses' | 'tree' | 'downloadPDF' | 'next' | 'nextSlide' | 'openInEditor' | 'prev' | 'prevSlide' | 'rawRoutes' | 'go'
14
13
 
15
14
  export interface SlidevContextNav extends Pick<typeof nav, SlidevContextNavKey> {
16
15
  route: ComputedRef<RouteRecordRaw | RouteLocationNormalizedLoaded>
17
16
  }
18
- export type SlidevContextNavClicks = Pick<typeof nav, SlidevContextNavClicksKey>
19
17
 
20
18
  export interface SlidevContext {
21
- nav: SlidevContextNav & SlidevContextNavClicks
19
+ nav: SlidevContextNav
22
20
  configs: typeof configs
23
21
  themeConfigs: ComputedRef<typeof configs['themeConfig']>
24
22
  }
@@ -26,10 +24,9 @@ export interface SlidevContext {
26
24
  export default function createSlidevContext() {
27
25
  return {
28
26
  install(app: App) {
29
- const context = reactive(useContext(route, clicks))
27
+ const context = reactive(useContext(route))
30
28
  app.provide(injectionSlidevContext, context)
31
29
  app.provide(injectionCurrentPage, computed(() => context.nav.currentPage))
32
- app.provide(injectionClicks, computed(() => context.nav.clicks))
33
30
 
34
31
  // allows controls from postMessages
35
32
  if (__DEV__) {