150 lines
4.0 KiB
TypeScript
150 lines
4.0 KiB
TypeScript
/**
|
|
* <v-clicks/> click animations component
|
|
*
|
|
* Learn more: https://sli.dev/guide/animations.html#click-animation
|
|
*/
|
|
|
|
import type { VNode, VNodeArrayChildren } from 'vue'
|
|
import { toArray } from '@antfu/utils'
|
|
import { Comment, createVNode, defineComponent, h, isVNode, resolveDirective, withDirectives } from 'vue'
|
|
import { normalizeSingleAtValue } from '../composables/useClicks'
|
|
import VClickGap from './VClickGap.vue'
|
|
|
|
const listTags = ['ul', 'ol']
|
|
|
|
export default defineComponent({
|
|
props: {
|
|
depth: {
|
|
type: [Number, String],
|
|
default: 1,
|
|
},
|
|
every: {
|
|
type: [Number, String],
|
|
default: 1,
|
|
},
|
|
at: {
|
|
type: [Number, String],
|
|
default: '+1',
|
|
},
|
|
hide: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
fade: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
handleSpecialElements: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
},
|
|
render() {
|
|
const every = +this.every
|
|
const at = normalizeSingleAtValue(this.at)
|
|
const isRelative = typeof at === 'string'
|
|
if (!at) {
|
|
console.warn('[slidev] Invalid at prop for v-clicks component:', at)
|
|
return
|
|
}
|
|
|
|
const click = resolveDirective('click')!
|
|
|
|
const applyDirective = (node: VNode, value: number | string) => {
|
|
return withDirectives(node, [[
|
|
click,
|
|
value,
|
|
'',
|
|
{
|
|
hide: this.hide,
|
|
fade: this.fade,
|
|
},
|
|
]])
|
|
}
|
|
|
|
const openAllTopLevelSlots = <T extends VNodeArrayChildren | VNode[]>(children: T): T => {
|
|
return children.flatMap((i) => {
|
|
if (isVNode(i) && typeof i.type === 'symbol' && Array.isArray(i.children))
|
|
return openAllTopLevelSlots(i.children)
|
|
else
|
|
return [i]
|
|
}) as T
|
|
}
|
|
|
|
let elements = this.$slots.default?.()
|
|
|
|
if (!elements)
|
|
return
|
|
|
|
elements = openAllTopLevelSlots(toArray(elements))
|
|
|
|
const mapSubList = (children: VNodeArrayChildren, depth = 1): VNodeArrayChildren => {
|
|
const vNodes = openAllTopLevelSlots(children).map((i) => {
|
|
if (!isVNode(i))
|
|
return i
|
|
if (listTags.includes(i.type as string) && Array.isArray(i.children)) {
|
|
// eslint-disable-next-line ts/no-use-before-define
|
|
const vNodes = mapChildren(i.children, depth + 1)
|
|
return h(i, {}, vNodes)
|
|
}
|
|
return h(i)
|
|
})
|
|
return vNodes
|
|
}
|
|
|
|
let globalIdx = 1
|
|
let execIdx = 0
|
|
const mapChildren = (children: VNodeArrayChildren, depth = 1): VNodeArrayChildren => {
|
|
const vNodes = openAllTopLevelSlots(children).map((i) => {
|
|
if (!isVNode(i) || i.type === Comment)
|
|
return i
|
|
|
|
const thisShowIdx = +at + Math.ceil(globalIdx++ / every) - 1
|
|
|
|
let vNode
|
|
if (depth < +this.depth && Array.isArray(i.children))
|
|
vNode = h(i, {}, mapSubList(i.children, depth))
|
|
else
|
|
vNode = h(i)
|
|
|
|
const delta = thisShowIdx - execIdx
|
|
execIdx = thisShowIdx
|
|
|
|
return applyDirective(
|
|
vNode,
|
|
isRelative
|
|
? delta >= 0 ? `+${delta}` : `${delta}`
|
|
: thisShowIdx,
|
|
)
|
|
})
|
|
return vNodes
|
|
}
|
|
|
|
const lastGap = () => createVNode(VClickGap, {
|
|
size: +at + Math.ceil((globalIdx - 1) / every) - 1 - execIdx,
|
|
})
|
|
|
|
if (this.handleSpecialElements) {
|
|
// handle ul, ol list
|
|
if (elements.length === 1 && listTags.includes(elements[0].type as string) && Array.isArray(elements[0].children))
|
|
return h(elements[0], {}, [...mapChildren(elements[0].children), lastGap()])
|
|
|
|
if (elements.length === 1 && elements[0].type as string === 'table') {
|
|
const tableNode = elements[0]
|
|
if (Array.isArray(tableNode.children)) {
|
|
return h(tableNode, {}, tableNode.children.map((i) => {
|
|
if (!isVNode(i))
|
|
return i
|
|
else if (i.type === 'tbody' && Array.isArray(i.children))
|
|
return h(i, {}, [...mapChildren(i.children), lastGap()])
|
|
else
|
|
return h(i)
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
|
|
return [...mapChildren(elements), lastGap()]
|
|
},
|
|
})
|