| 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 |