Skip to content

Commit

Permalink
feat: enhanced v-click usage
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed May 4, 2021
1 parent 919d522 commit 4054fca
Show file tree
Hide file tree
Showing 12 changed files with 134 additions and 67 deletions.
29 changes: 29 additions & 0 deletions docs/guide/animations.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,35 @@ When you click the "next" button, both `Hello` and `World` will show up together

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

### Custom Clicks Count

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:

```yaml
---
# 10 clicks in this slide, before going to the next
clicks: 10
---
```

### Ordering

By passing the click index to your directives, you can customize the order of the revealing

```md
<!-- "1" go first -->
<div v-click>1</div>
<div v-click>2</div>
<div v-click>3</div>
```

```md
<!-- "3" go first, then "2" -->
<div v-click="3">1</div>
<div v-click="2">2</div>
<div v-click="1">3</div>
```

## Transitions

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.
12 changes: 6 additions & 6 deletions packages/client/internals/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useCodeMirror } from '../setup/codemirror'
import { currentRoute, currentSlideId } from '../logic/nav'
import { useDynamicSlideInfo } from '../logic/note'
const tab = ref<'content' | 'note'>('content')
const clicks = ref<'content' | 'note'>('content')
const offsetRight = ref(0)
const content = ref('')
const note = ref('')
Expand Down Expand Up @@ -136,15 +136,15 @@ const editorLink = computed(() => {
>
<div class="flex pb-2 text-xl -mt-1">
<div class="mr-4 rounded flex">
<button class="icon-btn" :class="tab === 'content' ? 'text-primary' : ''" @click="tab='content'">
<button class="icon-btn" :class="clicks === 'content' ? 'text-primary' : ''" @click="clicks='content'">
<carbon:account />
</button>
<button class="icon-btn" :class="tab === 'note' ? 'text-primary' : ''" @click="tab='note'">
<button class="icon-btn" :class="clicks === 'note' ? 'text-primary' : ''" @click="clicks='note'">
<carbon:align-box-bottom-right />
</button>
</div>
<span class="text-2xl pt-1">
{{ tab === 'content' ? 'Slide' : 'Note' }}
{{ clicks === 'content' ? 'Slide' : 'Note' }}
</span>
<div class="flex-auto"></div>
<button class="icon-btn" :class="{ disabled: !dirty }" @click="save">
Expand All @@ -160,10 +160,10 @@ const editorLink = computed(() => {
</button>
</div>
<div class="h-full overflow-auto">
<div v-show="tab === 'content'" class="h-full overflow-auto">
<div v-show="clicks === 'content'" class="h-full overflow-auto">
<textarea ref="contentInput" />
</div>
<div v-show="tab === 'note'" class="h-full overflow-auto">
<div v-show="clicks === 'note'" class="h-full overflow-auto">
<textarea ref="noteInput" />
</div>
</div>
Expand Down
8 changes: 4 additions & 4 deletions packages/client/internals/Play.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref } from 'vue'
import { isPrintMode, showEditor, windowSize, isScreenVertical } from '../state'
import { next, prev, currentRoute, tab, tabElements, useSwipeControls } from '../logic/nav'
import { next, prev, currentRoute, clicks, clicksElements, useSwipeControls } from '../logic/nav'
import { registerShotcuts } from '../logic/shortcuts'
import Controls from './Controls.vue'
import SlideContainer from './SlideContainer.vue'
Expand All @@ -27,12 +27,12 @@ useSwipeControls(root)
<template>
<div id="page-root" ref="root" class="grid grid-cols-[1fr,max-content]">
<SlideContainer
v-model:tab="tab"
v-model:tab-elements="tabElements"
v-model:clicks="clicks"
v-model:clicks-elements="clicksElements"
class="w-full h-full bg-black"
:width="isPrintMode ? windowSize.width.value : undefined"
:route="currentRoute"
:tab-disabled="false"
:clicks-disabled="false"
@click="onClick"
>
<template #controls>
Expand Down
20 changes: 10 additions & 10 deletions packages/client/internals/Presenter.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { useHead } from '@vueuse/head'
import { ref, computed } from 'vue'
import { total, currentPage, currentRoute, nextRoute, tab, tabElements, useSwipeControls } from '../logic/nav'
import { total, currentPage, currentRoute, nextRoute, clicks, clicksElements, useSwipeControls, clicksTotal } from '../logic/nav'
import { showOverview } from '../state'
import { configs } from '../env'
import { registerShotcuts } from '../logic/shortcuts'
Expand All @@ -19,16 +19,16 @@ useHead({
const main = ref<HTMLDivElement>()
const nextTabElements = ref([])
const nextSlide = computed(() => {
if (tab.value < tabElements.value.length) {
if (clicks.value < clicksTotal.value) {
return {
route: currentRoute.value,
tab: tab.value + 1,
clicks: clicks.value + 1,
}
}
else {
return {
route: nextRoute.value,
tab: 0,
clicks: 0,
}
}
})
Expand All @@ -49,21 +49,21 @@ useSwipeControls(main)
<div ref="main" class="grid-section main flex flex-col p-4">
<SlideContainer
key="main"
v-model:tab="tab"
v-model:tab-elements="tabElements"
v-model:clicks="clicks"
v-model:clicks-elements="clicksElements"
class="h-full w-full"
:route="currentRoute"
:tab-disabled="false"
:clicks-disabled="false"
/>
</div>
<div class="grid-section next flex flex-col p-4">
<SlideContainer
key="next"
v-model:tab-elements="nextTabElements"
v-model:clicks-elements="nextTabElements"
class="h-full w-full"
:tab="nextSlide.tab"
:clicks="nextSlide.clicks"
:route="nextSlide.route"
:tab-disabled="false"
:clicks-disabled="false"
/>
</div>
<div class="grid-section note">
Expand Down
22 changes: 11 additions & 11 deletions packages/client/internals/SlideContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@ import { useElementSize, useVModel } from '@vueuse/core'
import { computed, defineProps, ref, watchEffect, provide, defineEmit } from 'vue'
import type { RouteRecordRaw } from 'vue-router'
import { slideAspect, slideWidth, slideHeight } from '../constants'
import { injectionTab, injectionTabDisabled, injectionTabElements } from '../modules/directives'
import { injectionClicks, injectionClicksDisabled, injectionClicksElements } from '../modules/directives'
const emit = defineEmit()
const props = defineProps({
width: {
type: Number,
},
tab: {
clicks: {
default: 0,
},
tabElements: {
clicksElements: {
default: () => [] as Element[],
},
tabDisabled: {
clicksDisabled: {
default: false,
},
meta: {
Expand All @@ -30,15 +30,15 @@ const props = defineProps({
},
})
const tab = useVModel(props, 'tab', emit)
const tabElements = useVModel(props, 'tabElements', emit)
const tabDisabled = useVModel(props, 'tabDisabled', emit)
const clicks = useVModel(props, 'clicks', emit)
const clicksElements = useVModel(props, 'clicksElements', emit)
const clicksDisabled = useVModel(props, 'clicksDisabled', emit)
tabElements.value = []
clicksElements.value = []
provide(injectionTab, tab)
provide(injectionTabElements, tabElements)
provide(injectionTabDisabled, tabDisabled)
provide(injectionClicks, clicks)
provide(injectionClicksDisabled, clicksDisabled)
provide(injectionClicksElements, clicksElements)
const root = ref<HTMLDivElement>()
const element = useElementSize(root)
Expand Down
2 changes: 1 addition & 1 deletion packages/client/internals/SlidesOverview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const cardWidth = computed(() => {
:key="route.path"
:width="cardWidth"
:route="route"
:tab-disabled="true"
:clicks-disabled="true"
class="pointer-events-none"
/>
</div>
Expand Down
26 changes: 14 additions & 12 deletions packages/client/logic/nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,33 @@ export const hasNext = computed(() => currentPage.value < rawRoutes.length - 1)
export const hasPrev = computed(() => currentPage.value > 0)
export const nextRoute = computed(() => rawRoutes.find(i => i.path === `${Math.min(rawRoutes.length - 1, currentPage.value + 1)}`))

export const tabElements = ref<HTMLElement[]>([])
export const tab = computed<number>({
export const clicksElements = ref<HTMLElement[]>([])
export const clicks = computed<number>({
get() {
let tab = +query.tab || 0
if (isNaN(tab))
tab = 0
return tab
let clicks = +query.clicks || 0
if (isNaN(clicks))
clicks = 0
return clicks
},
set(v) {
query.tab = v.toString()
query.clicks = v.toString()
},
})

export const clicksTotal = computed(() => +(currentRoute.value?.meta?.clicks ?? clicksElements.value.length))

export function next() {
if (tabElements.value.length <= tab.value)
if (clicksTotal.value <= clicks.value)
nextSlide()
else
tab.value += 1
clicks.value += 1
}

export async function prev() {
if (tab.value <= 0)
if (clicks.value <= 0)
prevSlide()
else
tab.value -= 1
clicks.value -= 1
}

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

export function go(page: number) {
tab.value = 0
clicks.value = 0
return router.push(getPath(page))
}

Expand Down
65 changes: 47 additions & 18 deletions packages/client/modules/directives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { App, DirectiveBinding, InjectionKey, Ref, watch } from 'vue'
import { remove } from '@antfu/utils'
import { isPrintMode } from '../state'

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

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

mounted(el: HTMLElement, dir) {
if (isPrintMode.value || dirInject(dir, injectionTabDisabled)!.value)
if (isPrintMode.value || dirInject(dir, injectionClicksDisabled)!.value)
return

const elements = dirInject(dir, injectionTabElements)!
const tab = dirInject(dir, injectionTab)!
const elements = dirInject(dir, injectionClicksElements)!
const clicks = dirInject(dir, injectionClicks)!

const prev = elements.value.length

if (!elements.value.includes(el))
elements.value.push(el)

watch(
tab,
clicks,
() => {
const show = tab.value > prev
el.classList.toggle('!opacity-0', !show)
el.classList.toggle('!pointer-events-none', !show)
const show = dir.value != null
? clicks.value >= dir.value
: clicks.value > prev
if (!el.classList.contains('v-click-hidden-explicitly'))
el.classList.toggle('v-click-hidden', !show)
},
{ immediate: true },
)
},
unmounted(el, dir) {
const elements = dirInject(dir, injectionTabElements)!
const elements = dirInject(dir, injectionClicksElements)!
if (elements?.value)
remove(elements.value, el)
},
Expand All @@ -51,25 +53,52 @@ export default function createDirectives() {
name: 'v-after',

mounted(el: HTMLElement, dir) {
if (isPrintMode.value || dirInject(dir, injectionTabDisabled)!.value)
if (isPrintMode.value || dirInject(dir, injectionClicksDisabled)!.value)
return

const elements = dirInject(dir, injectionTabElements)!
const tab = dirInject(dir, injectionTab)!
const elements = dirInject(dir, injectionClicksElements)!
const clicks = dirInject(dir, injectionClicks)!

const prev = elements.value.length

watch(
tab,
clicks,
() => {
const show = tab.value >= prev
el.classList.toggle('!opacity-0', !show)
el.classList.toggle('!pointer-events-none', !show)
const show = clicks.value >= (dir.value ?? prev)
if (!el.classList.contains('v-click-hidden-explicitly'))
el.classList.toggle('v-click-hidden', !show)
},
{ immediate: true },
)
},
})

app.directive('click-hide', {
// @ts-expect-error
name: 'v-click-hide',

mounted(el: HTMLElement, dir) {
if (isPrintMode.value || dirInject(dir, injectionClicksDisabled)!.value)
return

const clicks = dirInject(dir, injectionClicks)!

watch(
clicks,
() => {
const hide = clicks.value > dir.value
el.classList.toggle('v-click-hidden', hide)
el.classList.toggle('v-click-hidden-explicitly', hide)
},
{ immediate: true },
)
},
unmounted(el, dir) {
const elements = dirInject(dir, injectionClicksElements)!
if (elements?.value)
remove(elements.value, el)
},
})
},
}
}
3 changes: 3 additions & 0 deletions packages/client/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ export const router = createRouter({
declare module 'vue-router' {
interface RouteMeta {
layout: string
name?: string
class?: string
clicks?: number
slide?: {
start: number
end: number
Expand Down

0 comments on commit 4054fca

Please sign in to comment.