返回 slidev
VSwitch.ts
根目录 / packages / client / builtin / VSwitch.ts
1 import type { PropType, Ref, Slot, TransitionGroupProps, VNode } from 'vue'
2 import { recomputeAllPoppers } from 'floating-vue'
3 import { defineComponent, h, onMounted, onUnmounted, ref, TransitionGroup, watchEffect } from 'vue'
4 import { CLASS_VCLICK_CURRENT, CLASS_VCLICK_DISPLAY_NONE, CLASS_VCLICK_PRIOR, CLASS_VCLICK_TARGET, CLICKS_MAX } from '../constants'
5 import { useSlideContext } from '../context'
6 import { resolveTransition } from '../logic/transition'
7 import { makeId } from '../logic/utils'
8 import { hmrSkipTransition } from '../state'
9
10 export default defineComponent({
11 props: {
12 at: {
13 type: [Number, String],
14 default: '+1',
15 },
16 /**
17 * unmount or hide the content when it's not visible
18 */
19 unmount: {
20 type: Boolean,
21 default: false,
22 },
23 transition: {
24 type: [Object, String, Boolean] as PropType<TransitionGroupProps | string | false>,
25 default: false,
26 },
27 tag: {
28 type: String,
29 default: 'div',
30 },
31 childTag: {
32 type: String,
33 default: 'div',
34 },
35 },
36 setup({ at, unmount, transition, tag, childTag }, { slots }) {
37 const slotEntries = Object.entries(slots).sort((a, b) => -a[0].split('-')[0] + +b[0].split('-')[0])
38 const contents: [start: number, end: number, slot: Slot<any> | undefined, elRef: Ref<HTMLElement | undefined>][] = []
39
40 let lastStart: number | undefined
41 for (const [range, slot] of slotEntries) {
42 const elRef = ref<HTMLElement>()
43 if (Number.isFinite(+range)) {
44 contents.push([+range, lastStart ?? +range + 1, slot, elRef])
45 lastStart = +range
46 }
47 else {
48 const [start, end] = range.split('-').map(Number)
49 if (!Number.isFinite(start) || !Number.isFinite(end))
50 throw new Error(`Invalid range for v-switch: ${range}`)
51 contents.push([start, end, slot, elRef])
52 lastStart = start
53 }
54 }
55
56 const size = Math.max(...contents.map(c => c[1])) - 1
57 const id = makeId()
58 const offset = ref(0)
59
60 const { $clicksContext: clicks, $nav: nav } = useSlideContext()
61
62 onMounted(() => {
63 const clicksInfo = clicks.calculateSince(at, size)
64 if (!clicksInfo) {
65 offset.value = CLICKS_MAX
66 return
67 }
68 clicks.register(id, clicksInfo)
69 watchEffect(() => {
70 offset.value = clicksInfo.currentOffset.value + 1
71 })
72 })
73
74 onUnmounted(() => {
75 clicks.unregister(id)
76 })
77
78 function onAfterLeave() {
79 // Refer to SlidesShow.vue
80 hmrSkipTransition.value = true
81 recomputeAllPoppers()
82 }
83 const transitionProps = transition && {
84 ...resolveTransition(transition, nav.value.navDirection < 0),
85 tag,
86 onAfterLeave,
87 }
88
89 return () => {
90 const children: VNode[] = []
91 for (let i = contents.length - 1; i >= 0; i--) {
92 const [start, end, slot, ref] = contents[i]
93 const visible = start <= offset.value && offset.value < end
94 if (unmount && !visible)
95 continue
96 children.push(h(childTag, {
97 'key': i,
98 ref,
99 'class': [
100 CLASS_VCLICK_TARGET,
101 offset.value === start && CLASS_VCLICK_CURRENT,
102 offset.value >= end && CLASS_VCLICK_PRIOR,
103 !visible && CLASS_VCLICK_DISPLAY_NONE,
104 ].filter(Boolean),
105 'data-slidev-clicks-start': start,
106 'data-slidev-clicks-end': end,
107 }, slot?.()))
108 }
109 return transitionProps
110 ? h(TransitionGroup, hmrSkipTransition.value ? {} : transitionProps, () => children)
111 : h(tag, children)
112 }
113 },
114 })
115
115 lines TYPESCRIPT