Skip to content

Commit 4054fca

Browse files
committed
feat: enhanced v-click usage
1 parent 919d522 commit 4054fca

File tree

12 files changed

+134
-67
lines changed

12 files changed

+134
-67
lines changed

docs/guide/animations.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,35 @@ When you click the "next" button, both `Hello` and `World` will show up together
5252

5353
An item will become visible accordingly whenever you click "next".
5454

55+
### Custom Clicks Count
56+
57+
By default, Slidev can smartly count how many steps are needed before going next slide. And you can override it by passing the `clicks` frontmatter option:
58+
59+
```yaml
60+
---
61+
# 10 clicks in this slide, before going to the next
62+
clicks: 10
63+
---
64+
```
65+
66+
### Ordering
67+
68+
By passing the click index to your directives, you can customize the order of the revealing
69+
70+
```md
71+
<!-- "1" go first -->
72+
<div v-click>1</div>
73+
<div v-click>2</div>
74+
<div v-click>3</div>
75+
```
76+
77+
```md
78+
<!-- "3" go first, then "2" -->
79+
<div v-click="3">1</div>
80+
<div v-click="2">2</div>
81+
<div v-click="1">3</div>
82+
```
83+
5584
## Transitions
5685

5786
The built-in support for slides and elements transitions is NOT provided in the current version. We are planned add it in the next major version. Before that, you can still use your custom styles and libraries to do that.

packages/client/internals/Editor.vue

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { useCodeMirror } from '../setup/codemirror'
66
import { currentRoute, currentSlideId } from '../logic/nav'
77
import { useDynamicSlideInfo } from '../logic/note'
88
9-
const tab = ref<'content' | 'note'>('content')
9+
const clicks = ref<'content' | 'note'>('content')
1010
const offsetRight = ref(0)
1111
const content = ref('')
1212
const note = ref('')
@@ -136,15 +136,15 @@ const editorLink = computed(() => {
136136
>
137137
<div class="flex pb-2 text-xl -mt-1">
138138
<div class="mr-4 rounded flex">
139-
<button class="icon-btn" :class="tab === 'content' ? 'text-primary' : ''" @click="tab='content'">
139+
<button class="icon-btn" :class="clicks === 'content' ? 'text-primary' : ''" @click="clicks='content'">
140140
<carbon:account />
141141
</button>
142-
<button class="icon-btn" :class="tab === 'note' ? 'text-primary' : ''" @click="tab='note'">
142+
<button class="icon-btn" :class="clicks === 'note' ? 'text-primary' : ''" @click="clicks='note'">
143143
<carbon:align-box-bottom-right />
144144
</button>
145145
</div>
146146
<span class="text-2xl pt-1">
147-
{{ tab === 'content' ? 'Slide' : 'Note' }}
147+
{{ clicks === 'content' ? 'Slide' : 'Note' }}
148148
</span>
149149
<div class="flex-auto"></div>
150150
<button class="icon-btn" :class="{ disabled: !dirty }" @click="save">
@@ -160,10 +160,10 @@ const editorLink = computed(() => {
160160
</button>
161161
</div>
162162
<div class="h-full overflow-auto">
163-
<div v-show="tab === 'content'" class="h-full overflow-auto">
163+
<div v-show="clicks === 'content'" class="h-full overflow-auto">
164164
<textarea ref="contentInput" />
165165
</div>
166-
<div v-show="tab === 'note'" class="h-full overflow-auto">
166+
<div v-show="clicks === 'note'" class="h-full overflow-auto">
167167
<textarea ref="noteInput" />
168168
</div>
169169
</div>

packages/client/internals/Play.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts">
22
import { ref } from 'vue'
33
import { isPrintMode, showEditor, windowSize, isScreenVertical } from '../state'
4-
import { next, prev, currentRoute, tab, tabElements, useSwipeControls } from '../logic/nav'
4+
import { next, prev, currentRoute, clicks, clicksElements, useSwipeControls } from '../logic/nav'
55
import { registerShotcuts } from '../logic/shortcuts'
66
import Controls from './Controls.vue'
77
import SlideContainer from './SlideContainer.vue'
@@ -27,12 +27,12 @@ useSwipeControls(root)
2727
<template>
2828
<div id="page-root" ref="root" class="grid grid-cols-[1fr,max-content]">
2929
<SlideContainer
30-
v-model:tab="tab"
31-
v-model:tab-elements="tabElements"
30+
v-model:clicks="clicks"
31+
v-model:clicks-elements="clicksElements"
3232
class="w-full h-full bg-black"
3333
:width="isPrintMode ? windowSize.width.value : undefined"
3434
:route="currentRoute"
35-
:tab-disabled="false"
35+
:clicks-disabled="false"
3636
@click="onClick"
3737
>
3838
<template #controls>

packages/client/internals/Presenter.vue

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts">
22
import { useHead } from '@vueuse/head'
33
import { ref, computed } from 'vue'
4-
import { total, currentPage, currentRoute, nextRoute, tab, tabElements, useSwipeControls } from '../logic/nav'
4+
import { total, currentPage, currentRoute, nextRoute, clicks, clicksElements, useSwipeControls, clicksTotal } from '../logic/nav'
55
import { showOverview } from '../state'
66
import { configs } from '../env'
77
import { registerShotcuts } from '../logic/shortcuts'
@@ -19,16 +19,16 @@ useHead({
1919
const main = ref<HTMLDivElement>()
2020
const nextTabElements = ref([])
2121
const nextSlide = computed(() => {
22-
if (tab.value < tabElements.value.length) {
22+
if (clicks.value < clicksTotal.value) {
2323
return {
2424
route: currentRoute.value,
25-
tab: tab.value + 1,
25+
clicks: clicks.value + 1,
2626
}
2727
}
2828
else {
2929
return {
3030
route: nextRoute.value,
31-
tab: 0,
31+
clicks: 0,
3232
}
3333
}
3434
})
@@ -49,21 +49,21 @@ useSwipeControls(main)
4949
<div ref="main" class="grid-section main flex flex-col p-4">
5050
<SlideContainer
5151
key="main"
52-
v-model:tab="tab"
53-
v-model:tab-elements="tabElements"
52+
v-model:clicks="clicks"
53+
v-model:clicks-elements="clicksElements"
5454
class="h-full w-full"
5555
:route="currentRoute"
56-
:tab-disabled="false"
56+
:clicks-disabled="false"
5757
/>
5858
</div>
5959
<div class="grid-section next flex flex-col p-4">
6060
<SlideContainer
6161
key="next"
62-
v-model:tab-elements="nextTabElements"
62+
v-model:clicks-elements="nextTabElements"
6363
class="h-full w-full"
64-
:tab="nextSlide.tab"
64+
:clicks="nextSlide.clicks"
6565
:route="nextSlide.route"
66-
:tab-disabled="false"
66+
:clicks-disabled="false"
6767
/>
6868
</div>
6969
<div class="grid-section note">

packages/client/internals/SlideContainer.vue

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@ import { useElementSize, useVModel } from '@vueuse/core'
33
import { computed, defineProps, ref, watchEffect, provide, defineEmit } from 'vue'
44
import type { RouteRecordRaw } from 'vue-router'
55
import { slideAspect, slideWidth, slideHeight } from '../constants'
6-
import { injectionTab, injectionTabDisabled, injectionTabElements } from '../modules/directives'
6+
import { injectionClicks, injectionClicksDisabled, injectionClicksElements } from '../modules/directives'
77
88
const emit = defineEmit()
99
const props = defineProps({
1010
width: {
1111
type: Number,
1212
},
13-
tab: {
13+
clicks: {
1414
default: 0,
1515
},
16-
tabElements: {
16+
clicksElements: {
1717
default: () => [] as Element[],
1818
},
19-
tabDisabled: {
19+
clicksDisabled: {
2020
default: false,
2121
},
2222
meta: {
@@ -30,15 +30,15 @@ const props = defineProps({
3030
},
3131
})
3232
33-
const tab = useVModel(props, 'tab', emit)
34-
const tabElements = useVModel(props, 'tabElements', emit)
35-
const tabDisabled = useVModel(props, 'tabDisabled', emit)
33+
const clicks = useVModel(props, 'clicks', emit)
34+
const clicksElements = useVModel(props, 'clicksElements', emit)
35+
const clicksDisabled = useVModel(props, 'clicksDisabled', emit)
3636
37-
tabElements.value = []
37+
clicksElements.value = []
3838
39-
provide(injectionTab, tab)
40-
provide(injectionTabElements, tabElements)
41-
provide(injectionTabDisabled, tabDisabled)
39+
provide(injectionClicks, clicks)
40+
provide(injectionClicksDisabled, clicksDisabled)
41+
provide(injectionClicksElements, clicksElements)
4242
4343
const root = ref<HTMLDivElement>()
4444
const element = useElementSize(root)

packages/client/internals/SlidesOverview.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const cardWidth = computed(() => {
5555
:key="route.path"
5656
:width="cardWidth"
5757
:route="route"
58-
:tab-disabled="true"
58+
:clicks-disabled="true"
5959
class="pointer-events-none"
6060
/>
6161
</div>

packages/client/logic/nav.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,31 +22,33 @@ export const hasNext = computed(() => currentPage.value < rawRoutes.length - 1)
2222
export const hasPrev = computed(() => currentPage.value > 0)
2323
export const nextRoute = computed(() => rawRoutes.find(i => i.path === `${Math.min(rawRoutes.length - 1, currentPage.value + 1)}`))
2424

25-
export const tabElements = ref<HTMLElement[]>([])
26-
export const tab = computed<number>({
25+
export const clicksElements = ref<HTMLElement[]>([])
26+
export const clicks = computed<number>({
2727
get() {
28-
let tab = +query.tab || 0
29-
if (isNaN(tab))
30-
tab = 0
31-
return tab
28+
let clicks = +query.clicks || 0
29+
if (isNaN(clicks))
30+
clicks = 0
31+
return clicks
3232
},
3333
set(v) {
34-
query.tab = v.toString()
34+
query.clicks = v.toString()
3535
},
3636
})
3737

38+
export const clicksTotal = computed(() => +(currentRoute.value?.meta?.clicks ?? clicksElements.value.length))
39+
3840
export function next() {
39-
if (tabElements.value.length <= tab.value)
41+
if (clicksTotal.value <= clicks.value)
4042
nextSlide()
4143
else
42-
tab.value += 1
44+
clicks.value += 1
4345
}
4446

4547
export async function prev() {
46-
if (tab.value <= 0)
48+
if (clicks.value <= 0)
4749
prevSlide()
4850
else
49-
tab.value -= 1
51+
clicks.value -= 1
5052
}
5153

5254
export function getPath(no: number | string) {
@@ -64,7 +66,7 @@ export function prevSlide() {
6466
}
6567

6668
export function go(page: number) {
67-
tab.value = 0
69+
clicks.value = 0
6870
return router.push(getPath(page))
6971
}
7072

packages/client/modules/directives.ts

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { App, DirectiveBinding, InjectionKey, Ref, watch } from 'vue'
22
import { remove } from '@antfu/utils'
33
import { isPrintMode } from '../state'
44

5-
export const injectionTab: InjectionKey<Ref<number>> = Symbol('v-click-tab')
6-
export const injectionTabElements: InjectionKey<Ref<Element[]>> = Symbol('v-click-tab-elements')
7-
export const injectionTabDisabled: InjectionKey<Ref<boolean>> = Symbol('v-click-tab-disabled')
5+
export const injectionClicks: InjectionKey<Ref<number>> = Symbol('v-click-clicks')
6+
export const injectionClicksElements: InjectionKey<Ref<Element[]>> = Symbol('v-click-clicks-elements')
7+
export const injectionClicksDisabled: InjectionKey<Ref<boolean>> = Symbol('v-click-clicks-disabled')
88

99
function dirInject<T = unknown>(dir: DirectiveBinding<any>, key: InjectionKey<T> | string, defaultValue?: T): T | undefined {
1010
return (dir.instance?.$ as any).provides[key as any] ?? defaultValue
@@ -18,29 +18,31 @@ export default function createDirectives() {
1818
name: 'v-click',
1919

2020
mounted(el: HTMLElement, dir) {
21-
if (isPrintMode.value || dirInject(dir, injectionTabDisabled)!.value)
21+
if (isPrintMode.value || dirInject(dir, injectionClicksDisabled)!.value)
2222
return
2323

24-
const elements = dirInject(dir, injectionTabElements)!
25-
const tab = dirInject(dir, injectionTab)!
24+
const elements = dirInject(dir, injectionClicksElements)!
25+
const clicks = dirInject(dir, injectionClicks)!
2626

2727
const prev = elements.value.length
2828

2929
if (!elements.value.includes(el))
3030
elements.value.push(el)
3131

3232
watch(
33-
tab,
33+
clicks,
3434
() => {
35-
const show = tab.value > prev
36-
el.classList.toggle('!opacity-0', !show)
37-
el.classList.toggle('!pointer-events-none', !show)
35+
const show = dir.value != null
36+
? clicks.value >= dir.value
37+
: clicks.value > prev
38+
if (!el.classList.contains('v-click-hidden-explicitly'))
39+
el.classList.toggle('v-click-hidden', !show)
3840
},
3941
{ immediate: true },
4042
)
4143
},
4244
unmounted(el, dir) {
43-
const elements = dirInject(dir, injectionTabElements)!
45+
const elements = dirInject(dir, injectionClicksElements)!
4446
if (elements?.value)
4547
remove(elements.value, el)
4648
},
@@ -51,25 +53,52 @@ export default function createDirectives() {
5153
name: 'v-after',
5254

5355
mounted(el: HTMLElement, dir) {
54-
if (isPrintMode.value || dirInject(dir, injectionTabDisabled)!.value)
56+
if (isPrintMode.value || dirInject(dir, injectionClicksDisabled)!.value)
5557
return
5658

57-
const elements = dirInject(dir, injectionTabElements)!
58-
const tab = dirInject(dir, injectionTab)!
59+
const elements = dirInject(dir, injectionClicksElements)!
60+
const clicks = dirInject(dir, injectionClicks)!
5961

6062
const prev = elements.value.length
6163

6264
watch(
63-
tab,
65+
clicks,
6466
() => {
65-
const show = tab.value >= prev
66-
el.classList.toggle('!opacity-0', !show)
67-
el.classList.toggle('!pointer-events-none', !show)
67+
const show = clicks.value >= (dir.value ?? prev)
68+
if (!el.classList.contains('v-click-hidden-explicitly'))
69+
el.classList.toggle('v-click-hidden', !show)
6870
},
6971
{ immediate: true },
7072
)
7173
},
7274
})
75+
76+
app.directive('click-hide', {
77+
// @ts-expect-error
78+
name: 'v-click-hide',
79+
80+
mounted(el: HTMLElement, dir) {
81+
if (isPrintMode.value || dirInject(dir, injectionClicksDisabled)!.value)
82+
return
83+
84+
const clicks = dirInject(dir, injectionClicks)!
85+
86+
watch(
87+
clicks,
88+
() => {
89+
const hide = clicks.value > dir.value
90+
el.classList.toggle('v-click-hidden', hide)
91+
el.classList.toggle('v-click-hidden-explicitly', hide)
92+
},
93+
{ immediate: true },
94+
)
95+
},
96+
unmounted(el, dir) {
97+
const elements = dirInject(dir, injectionClicksElements)!
98+
if (elements?.value)
99+
remove(elements.value, el)
100+
},
101+
})
73102
},
74103
}
75104
}

packages/client/routes.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ export const router = createRouter({
4040
declare module 'vue-router' {
4141
interface RouteMeta {
4242
layout: string
43+
name?: string
44+
class?: string
45+
clicks?: number
4346
slide?: {
4447
start: number
4548
end: number

0 commit comments

Comments
 (0)